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.
- checksums.yaml +4 -4
- data/README.adoc +33 -11
- data/conformance/pending.yaml +4 -0
- data/lib/pubid/adobe/identifier.rb +1 -3
- data/lib/pubid/adobe/parser.rb +10 -2
- data/lib/pubid/adobe.rb +1 -0
- data/lib/pubid/amca/builder.rb +2 -2
- data/lib/pubid/amca/identifiers/base.rb +49 -27
- data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
- data/lib/pubid/amca/identifiers/publication.rb +13 -9
- data/lib/pubid/amca/identifiers/standard.rb +2 -0
- data/lib/pubid/amca/parser.rb +1 -1
- data/lib/pubid/amca/renderer.rb +4 -4
- data/lib/pubid/amca/urn_generator.rb +2 -2
- data/lib/pubid/amca.rb +2 -1
- data/lib/pubid/ansi/builder.rb +2 -2
- data/lib/pubid/ansi/identifier.rb +18 -0
- data/lib/pubid/ansi/parser.rb +1 -1
- data/lib/pubid/ansi/renderer.rb +2 -2
- data/lib/pubid/ansi.rb +5 -5
- data/lib/pubid/api/builder.rb +16 -2
- data/lib/pubid/api/identifier.rb +25 -4
- data/lib/pubid/api/identifiers/mpms.rb +10 -8
- data/lib/pubid/api/parser.rb +1 -1
- data/lib/pubid/api/renderer.rb +16 -7
- data/lib/pubid/api/single_identifier.rb +5 -12
- data/lib/pubid/api.rb +1 -0
- data/lib/pubid/ashrae/builder.rb +31 -10
- data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
- data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
- data/lib/pubid/ashrae/identifiers/base.rb +81 -5
- data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
- data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
- data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
- data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
- data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
- data/lib/pubid/ashrae/parser.rb +23 -8
- data/lib/pubid/ashrae/renderer.rb +6 -6
- data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
- data/lib/pubid/ashrae/urn_generator.rb +8 -2
- data/lib/pubid/ashrae.rb +7 -1
- data/lib/pubid/asme/builder.rb +11 -2
- data/lib/pubid/asme/identifier.rb +9 -0
- data/lib/pubid/asme/identifiers/standard.rb +86 -2
- data/lib/pubid/asme/parser.rb +1 -1
- data/lib/pubid/asme/renderer.rb +3 -3
- data/lib/pubid/asme/single_identifier.rb +8 -1
- data/lib/pubid/asme/urn_generator.rb +2 -2
- data/lib/pubid/asme.rb +1 -0
- data/lib/pubid/astm/builder.rb +10 -3
- data/lib/pubid/astm/identifier.rb +9 -0
- data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
- data/lib/pubid/astm/identifiers/code_number.rb +81 -0
- data/lib/pubid/astm/identifiers/data_series.rb +2 -0
- data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
- data/lib/pubid/astm/identifiers/manual.rb +10 -0
- data/lib/pubid/astm/identifiers/monograph.rb +2 -0
- data/lib/pubid/astm/identifiers/research_report.rb +10 -0
- data/lib/pubid/astm/identifiers/standard.rb +10 -0
- data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
- data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
- data/lib/pubid/astm/identifiers.rb +1 -0
- data/lib/pubid/astm/parser.rb +1 -1
- data/lib/pubid/astm/renderer.rb +1 -1
- data/lib/pubid/astm/single_identifier.rb +31 -1
- data/lib/pubid/astm/urn_generator.rb +7 -5
- data/lib/pubid/astm.rb +1 -0
- data/lib/pubid/bipm/builder.rb +66 -10
- data/lib/pubid/bipm/identifier.rb +134 -9
- data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
- data/lib/pubid/bipm/identifiers/guide.rb +11 -0
- data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
- data/lib/pubid/bipm/identifiers/mep.rb +12 -0
- data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
- data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
- data/lib/pubid/bipm/parser.rb +65 -12
- data/lib/pubid/bipm/renderer.rb +8 -0
- data/lib/pubid/bipm/urn_parser.rb +11 -3
- data/lib/pubid/bipm.rb +7 -1
- data/lib/pubid/bsi/builder.rb +48 -38
- data/lib/pubid/bsi/components/date.rb +10 -4
- data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
- data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
- data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +1 -2
- data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
- data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
- data/lib/pubid/bsi/identifiers/handbook.rb +1 -3
- data/lib/pubid/bsi/identifiers/practice_guide.rb +1 -2
- data/lib/pubid/bsi/identifiers/set.rb +11 -0
- data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
- data/lib/pubid/bsi/parser.rb +1 -1
- data/lib/pubid/bsi/renderer.rb +63 -51
- data/lib/pubid/bsi/single_identifier.rb +27 -9
- data/lib/pubid/bsi/urn_generator.rb +11 -2
- data/lib/pubid/bsi.rb +7 -1
- data/lib/pubid/builder/base.rb +18 -8
- data/lib/pubid/bundled_identifier.rb +16 -6
- data/lib/pubid/calconnect/identifier.rb +9 -5
- data/lib/pubid/calconnect/parser.rb +1 -1
- data/lib/pubid/calconnect.rb +7 -1
- data/lib/pubid/ccsds/identifier.rb +13 -2
- data/lib/pubid/ccsds/identifiers/base.rb +4 -2
- data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
- data/lib/pubid/ccsds/parser.rb +1 -1
- data/lib/pubid/ccsds/single_identifier.rb +16 -12
- data/lib/pubid/ccsds.rb +1 -0
- data/lib/pubid/cen_cenelec/builder.rb +101 -76
- data/lib/pubid/cen_cenelec/identifier.rb +115 -3
- data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
- data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
- data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
- data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
- data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
- data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
- data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
- data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
- data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
- data/lib/pubid/cen_cenelec/parser.rb +1 -1
- data/lib/pubid/cen_cenelec/renderer.rb +55 -60
- data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
- data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
- data/lib/pubid/cen_cenelec.rb +28 -10
- data/lib/pubid/cie/builder.rb +10 -0
- data/lib/pubid/cie/identifier.rb +6 -1
- data/lib/pubid/cie/identifiers/bundle.rb +4 -2
- data/lib/pubid/cie/identifiers/conference.rb +2 -2
- data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
- data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
- data/lib/pubid/cie/identifiers/identical.rb +2 -2
- data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
- data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
- data/lib/pubid/cie/identifiers/standard.rb +22 -13
- data/lib/pubid/cie/identifiers/supplement.rb +2 -2
- data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
- data/lib/pubid/cie/parser.rb +1 -1
- data/lib/pubid/cie.rb +7 -1
- data/lib/pubid/conformance/checks.rb +72 -0
- data/lib/pubid/conformance/corpus/case.rb +55 -0
- data/lib/pubid/conformance/corpus.rb +36 -0
- data/lib/pubid/conformance/generator.rb +212 -0
- data/lib/pubid/conformance/pending.rb +63 -0
- data/lib/pubid/conformance/runner.rb +153 -0
- data/lib/pubid/conformance.rb +55 -0
- data/lib/pubid/core.rb +2 -0
- data/lib/pubid/csa/builder.rb +47 -47
- data/lib/pubid/csa/composite_identifier.rb +14 -19
- data/lib/pubid/csa/identifier.rb +143 -81
- data/lib/pubid/csa/identifiers/bundled.rb +23 -13
- data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
- data/lib/pubid/csa/identifiers/cec.rb +44 -4
- data/lib/pubid/csa/identifiers/combined.rb +95 -73
- data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
- data/lib/pubid/csa/identifiers/package.rb +2 -2
- data/lib/pubid/csa/parser.rb +6 -2
- data/lib/pubid/csa/renderer.rb +13 -5
- data/lib/pubid/csa/single_identifier.rb +93 -4
- data/lib/pubid/csa/urn_generator.rb +9 -2
- data/lib/pubid/csa/wrapper_identifier.rb +39 -23
- data/lib/pubid/csa.rb +7 -1
- data/lib/pubid/doi/identifier.rb +6 -3
- data/lib/pubid/doi/parser.rb +1 -1
- data/lib/pubid/doi.rb +1 -0
- data/lib/pubid/easc/identifier.rb +0 -2
- data/lib/pubid/easc/parser.rb +10 -2
- data/lib/pubid/easc.rb +1 -0
- data/lib/pubid/ecma/builder.rb +23 -9
- data/lib/pubid/ecma/identifier.rb +81 -10
- data/lib/pubid/ecma/parser.rb +39 -4
- data/lib/pubid/ecma/renderer.rb +29 -8
- data/lib/pubid/ecma/urn_generator.rb +34 -13
- data/lib/pubid/ecma/urn_parser.rb +30 -14
- data/lib/pubid/ecma.rb +1 -0
- data/lib/pubid/errors.rb +80 -0
- data/lib/pubid/etsi/builder.rb +6 -1
- data/lib/pubid/etsi/identifiers/base.rb +34 -20
- data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
- data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
- data/lib/pubid/etsi/parser.rb +1 -1
- data/lib/pubid/etsi.rb +1 -0
- data/lib/pubid/evs/builder.rb +26 -0
- data/lib/pubid/evs/identifier.rb +37 -0
- data/lib/pubid/evs/identifiers/national_adoption.rb +22 -0
- data/lib/pubid/evs/identifiers.rb +9 -0
- data/lib/pubid/evs/parser.rb +27 -0
- data/lib/pubid/evs/renderer.rb +30 -0
- data/lib/pubid/evs/urn_generator.rb +30 -0
- data/lib/pubid/evs/urn_parser.rb +80 -0
- data/lib/pubid/evs.rb +90 -0
- data/lib/pubid/export.rb +1 -0
- data/lib/pubid/format_detector.rb +1 -0
- data/lib/pubid/gb/builder.rb +14 -9
- data/lib/pubid/gb/identifier.rb +29 -7
- data/lib/pubid/gb/parser.rb +5 -1
- data/lib/pubid/gb/renderer.rb +4 -3
- data/lib/pubid/gb.rb +2 -0
- data/lib/pubid/gost/builder.rb +46 -9
- data/lib/pubid/gost/identifier.rb +0 -2
- data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
- data/lib/pubid/gost/parser.rb +10 -2
- data/lib/pubid/gost.rb +1 -0
- data/lib/pubid/iala/identifier.rb +9 -6
- data/lib/pubid/iala/parser.rb +10 -2
- data/lib/pubid/iala/urn_parser.rb +6 -2
- data/lib/pubid/iala.rb +1 -0
- data/lib/pubid/iana/builder.rb +3 -1
- data/lib/pubid/iana/identifier.rb +74 -6
- data/lib/pubid/iana/identifiers/registry.rb +36 -0
- data/lib/pubid/iana/parser.rb +1 -1
- data/lib/pubid/iana/renderer.rb +3 -0
- data/lib/pubid/iana/urn_generator.rb +5 -1
- data/lib/pubid/iana.rb +1 -0
- data/lib/pubid/identifier.rb +605 -13
- data/lib/pubid/identifier_metadata.rb +1 -0
- data/lib/pubid/idf/builder.rb +3 -3
- data/lib/pubid/idf/identifier.rb +25 -9
- data/lib/pubid/idf/identifiers/amendment.rb +1 -1
- data/lib/pubid/idf/identifiers/corrigendum.rb +1 -1
- data/lib/pubid/idf/identifiers/international_standard.rb +1 -1
- data/lib/pubid/idf/identifiers/reviewed_method.rb +1 -1
- data/lib/pubid/idf/parser.rb +1 -1
- data/lib/pubid/idf/renderer.rb +4 -4
- data/lib/pubid/idf/single_identifier.rb +1 -1
- data/lib/pubid/idf/urn_generator.rb +1 -1
- data/lib/pubid/idf.rb +7 -1
- data/lib/pubid/iec/builder.rb +14 -7
- data/lib/pubid/iec/components.rb +0 -1
- data/lib/pubid/iec/identifier.rb +73 -83
- data/lib/pubid/iec/identifiers/amendment.rb +6 -2
- data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
- data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
- data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
- data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
- data/lib/pubid/iec/identifiers/guide.rb +0 -16
- data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
- data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
- data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
- data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
- data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
- data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
- data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
- data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
- data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
- data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
- data/lib/pubid/iec/identifiers/working_document.rb +24 -10
- data/lib/pubid/iec/parser.rb +2 -18
- data/lib/pubid/iec/renderer.rb +13 -10
- data/lib/pubid/iec/single_identifier.rb +33 -10
- data/lib/pubid/iec/urn_generator.rb +227 -61
- data/lib/pubid/iec/urn_parser.rb +42 -4
- data/lib/pubid/iec.rb +1 -0
- data/lib/pubid/ieee/aiee/identifier.rb +2 -2
- data/lib/pubid/ieee/aiee/parser.rb +1 -1
- data/lib/pubid/ieee/builder.rb +87 -3
- data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
- data/lib/pubid/ieee/identifiers/base.rb +42 -5
- data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
- data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
- data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
- data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
- data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
- data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
- data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
- data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
- data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
- data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
- data/lib/pubid/ieee/identifiers/si_standard.rb +5 -2
- data/lib/pubid/ieee/ire/identifier.rb +2 -2
- data/lib/pubid/ieee/ire/parser.rb +20 -2
- data/lib/pubid/ieee/nesc/parser.rb +1 -1
- data/lib/pubid/ieee/parser.rb +208 -33
- data/lib/pubid/ieee/project_renderer.rb +46 -0
- data/lib/pubid/ieee/renderer.rb +11 -8
- data/lib/pubid/ieee/urn_generator.rb +32 -3
- data/lib/pubid/ieee.rb +9 -1
- data/lib/pubid/ieee_debug.rb +1 -0
- data/lib/pubid/ietf/builder.rb +24 -8
- data/lib/pubid/ietf/identifiers/base.rb +25 -28
- data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
- data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
- data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
- data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
- data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
- data/lib/pubid/ietf/identifiers/std.rb +11 -0
- data/lib/pubid/ietf/identifiers.rb +1 -0
- data/lib/pubid/ietf/parser.rb +27 -9
- data/lib/pubid/ietf/renderer.rb +44 -6
- data/lib/pubid/ietf/urn_generator.rb +20 -6
- data/lib/pubid/ietf/urn_parser.rb +10 -2
- data/lib/pubid/ietf.rb +12 -2
- data/lib/pubid/iho/identifiers/base.rb +10 -3
- data/lib/pubid/iho/parser.rb +1 -1
- data/lib/pubid/iho.rb +1 -0
- data/lib/pubid/isbn/identifier.rb +25 -7
- data/lib/pubid/isbn/parser.rb +1 -1
- data/lib/pubid/isbn.rb +1 -0
- data/lib/pubid/iso/builder.rb +10 -11
- data/lib/pubid/iso/bundled_identifier.rb +5 -3
- data/lib/pubid/iso/combined_identifier.rb +5 -3
- data/lib/pubid/iso/components.rb +0 -1
- data/lib/pubid/iso/identifier.rb +23 -11
- data/lib/pubid/iso/identifiers/directives.rb +7 -4
- data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
- data/lib/pubid/iso/identifiers/tc_document.rb +31 -46
- data/lib/pubid/iso/normalizer.rb +1 -1
- data/lib/pubid/iso/parser.rb +1 -1
- data/lib/pubid/iso/urn_generator.rb +10 -10
- data/lib/pubid/iso.rb +12 -5
- data/lib/pubid/itu/builder.rb +10 -0
- data/lib/pubid/itu/identifiers/addendum.rb +2 -2
- data/lib/pubid/itu/identifiers/amendment.rb +2 -2
- data/lib/pubid/itu/identifiers/base.rb +29 -5
- data/lib/pubid/itu/identifiers/contribution.rb +31 -0
- data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
- data/lib/pubid/itu/identifiers/errata.rb +2 -2
- data/lib/pubid/itu/identifiers/supplement.rb +2 -2
- data/lib/pubid/itu/identifiers.rb +1 -0
- data/lib/pubid/itu/parser.rb +20 -3
- data/lib/pubid/itu.rb +1 -0
- data/lib/pubid/jcgm/builder.rb +1 -1
- data/lib/pubid/jcgm/identifier.rb +10 -0
- data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
- data/lib/pubid/jcgm/parser.rb +10 -3
- data/lib/pubid/jcgm/renderer.rb +10 -3
- data/lib/pubid/jcgm/single_identifier.rb +24 -15
- data/lib/pubid/jcgm/urn_generator.rb +1 -1
- data/lib/pubid/jcgm/urn_parser.rb +156 -14
- data/lib/pubid/jcgm.rb +10 -0
- data/lib/pubid/jis/identifier.rb +10 -3
- data/lib/pubid/jis/parser.rb +1 -1
- data/lib/pubid/jis.rb +1 -0
- data/lib/pubid/nist/builder.rb +12 -12
- data/lib/pubid/nist/components/stage.rb +2 -2
- data/lib/pubid/nist/configuration.rb +6 -8
- data/lib/pubid/nist/identifiers/base.rb +22 -5
- data/lib/pubid/nist/identifiers/circular.rb +9 -2
- data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
- data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
- data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
- data/lib/pubid/nist/identifiers/crpl_report.rb +12 -7
- data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +2 -2
- data/lib/pubid/nist/identifiers/handbook.rb +9 -2
- data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
- data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +16 -8
- data/lib/pubid/nist/identifiers/monograph.rb +14 -7
- data/lib/pubid/nist/identifiers/report.rb +14 -6
- data/lib/pubid/nist/parser.rb +1 -1
- data/lib/pubid/nist/series/ir.rb +3 -7
- data/lib/pubid/nist/supplement_identifier.rb +16 -1
- data/lib/pubid/nist.rb +7 -1
- data/lib/pubid/oasis/builder.rb +12 -10
- data/lib/pubid/oasis/identifier.rb +89 -11
- data/lib/pubid/oasis/parser.rb +1 -1
- data/lib/pubid/oasis.rb +7 -1
- data/lib/pubid/ogc/identifier.rb +9 -5
- data/lib/pubid/ogc/parser.rb +1 -1
- data/lib/pubid/ogc.rb +1 -0
- data/lib/pubid/oiml/builder.rb +7 -3
- data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
- data/lib/pubid/oiml/identifiers/bulletin.rb +40 -10
- data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
- data/lib/pubid/oiml/identifiers/document.rb +2 -0
- data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
- data/lib/pubid/oiml/identifiers/guide.rb +2 -0
- data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
- data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
- data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
- data/lib/pubid/oiml/identifiers.rb +1 -0
- data/lib/pubid/oiml/parser.rb +8 -3
- data/lib/pubid/oiml/renderer.rb +3 -3
- data/lib/pubid/oiml/single_identifier.rb +26 -42
- data/lib/pubid/oiml/supplement_identifier.rb +27 -0
- data/lib/pubid/oiml/urn_generator.rb +4 -4
- data/lib/pubid/oiml.rb +7 -1
- data/lib/pubid/omg/builder.rb +1 -0
- data/lib/pubid/omg/identifier.rb +20 -3
- data/lib/pubid/omg/parser.rb +64 -11
- data/lib/pubid/omg/renderer.rb +13 -1
- data/lib/pubid/omg.rb +2 -1
- data/lib/pubid/parser/grammar.rb +74 -0
- data/lib/pubid/parser.rb +2 -0
- data/lib/pubid/parsers/mr_string.rb +19 -0
- data/lib/pubid/plateau/parser.rb +1 -1
- data/lib/pubid/plateau.rb +10 -0
- data/lib/pubid/prefixes_support.rb +1 -0
- data/lib/pubid/renderers/annotator.rb +233 -0
- data/lib/pubid/renderers/base.rb +13 -0
- data/lib/pubid/renderers/directives_renderer.rb +6 -4
- data/lib/pubid/renderers/human_readable.rb +3 -3
- data/lib/pubid/renderers/mr_string.rb +15 -9
- data/lib/pubid/renderers.rb +1 -0
- data/lib/pubid/rendering/numbering.rb +25 -7
- data/lib/pubid/rendering.rb +1 -0
- data/lib/pubid/sae/builder.rb +1 -1
- data/lib/pubid/sae/identifiers/base.rb +13 -3
- data/lib/pubid/sae/parser.rb +1 -1
- data/lib/pubid/sae/urn_generator.rb +1 -1
- data/lib/pubid/sae.rb +1 -0
- data/lib/pubid/schema/declaration.rb +33 -0
- data/lib/pubid/schema/error.rb +14 -0
- data/lib/pubid/schema/identifier_type.rb +23 -0
- data/lib/pubid/schema/loader.rb +117 -0
- data/lib/pubid/schema/typed_stage.rb +24 -0
- data/lib/pubid/schema.rb +25 -0
- data/lib/pubid/tgpp/builder.rb +1 -1
- data/lib/pubid/tgpp/identifier.rb +21 -5
- data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
- data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
- data/lib/pubid/tgpp/parser.rb +22 -6
- data/lib/pubid/tgpp/renderer.rb +21 -5
- data/lib/pubid/tgpp/urn_generator.rb +27 -8
- data/lib/pubid/tgpp/urn_parser.rb +8 -6
- data/lib/pubid/tgpp.rb +7 -1
- data/lib/pubid/type_resolver.rb +16 -1
- data/lib/pubid/un/identifier.rb +6 -3
- data/lib/pubid/un/parser.rb +1 -1
- data/lib/pubid/un.rb +1 -0
- data/lib/pubid/urn_parser/errors.rb +8 -1
- data/lib/pubid/urn_parser.rb +1 -0
- data/lib/pubid/utils.rb +1 -0
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid/w3c/builder.rb +5 -5
- data/lib/pubid/w3c/identifier.rb +38 -9
- data/lib/pubid/w3c/parser.rb +1 -1
- data/lib/pubid/w3c/renderer.rb +1 -1
- data/lib/pubid/w3c/urn_generator.rb +2 -2
- data/lib/pubid/w3c.rb +1 -0
- data/lib/pubid/xsf/identifier.rb +9 -5
- data/lib/pubid/xsf/parser.rb +17 -4
- data/lib/pubid/xsf.rb +7 -1
- data/lib/pubid.rb +399 -21
- data/lib/tasks/conformance.rake +34 -0
- data/lib/tasks/docs.rake +16 -14
- data/lib/tasks/schema.rake +88 -0
- data/schema/core/joint_prefixes.yaml +24 -0
- data/schema/iec.yaml +1017 -0
- data/schema/iso.yaml +1586 -0
- data/schema/schema.schema.yaml +96 -0
- metadata +40 -19
- data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
- data/archived-gems/pubid-iec/stages.yaml +0 -129
- data/archived-gems/pubid-iec/update_codes.yaml +0 -67
- data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
- data/archived-gems/pubid-iso/stages.yaml +0 -106
- data/archived-gems/pubid-iso/update_codes.yaml +0 -4
- data/archived-gems/pubid-itu/i18n.yaml +0 -13
- data/archived-gems/pubid-itu/series.yaml +0 -42
- data/archived-gems/pubid-nist/publishers.yaml +0 -6
- data/archived-gems/pubid-nist/series.yaml +0 -121
- data/archived-gems/pubid-nist/update_codes.yaml +0 -93
- data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
- data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +0 -70
- data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
- data/lib/pubid/iec/components/code.rb +0 -36
- data/lib/pubid/iso/components/code.rb +0 -24
- /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
data/lib/pubid.rb
CHANGED
|
@@ -15,6 +15,8 @@ require "pubid/lutaml/no_store_registration"
|
|
|
15
15
|
require "pubid/prefixes_support"
|
|
16
16
|
|
|
17
17
|
module Pubid
|
|
18
|
+
autoload :Schema, "pubid/schema"
|
|
19
|
+
autoload :Conformance, "pubid/conformance"
|
|
18
20
|
# Upper bound on the length of an identifier string accepted by any +parse+
|
|
19
21
|
# entry point. Real-world standards identifiers are well under 200 characters;
|
|
20
22
|
# this limit exists purely to keep pathological, attacker-controlled inputs
|
|
@@ -30,12 +32,28 @@ module Pubid
|
|
|
30
32
|
INPUT_TOO_LONG_MESSAGE =
|
|
31
33
|
"identifier string exceeds maximum length of #{MAX_INPUT_LENGTH} characters"
|
|
32
34
|
|
|
35
|
+
# Raised (as an ArgumentError) when +parse+ gets something that is not a
|
|
36
|
+
# String. Without this check the input reaches +.length+ and the caller gets a
|
|
37
|
+
# NoMethodError ("undefined method 'length' for nil"), a Ruby internal error
|
|
38
|
+
# that says nothing about the identifier contract. Every public +parse+ entry
|
|
39
|
+
# point rejects a non-String before it measures the length.
|
|
40
|
+
INPUT_NOT_A_STRING_MESSAGE = "identifier must be a String"
|
|
41
|
+
|
|
33
42
|
# Registry for tracking all loaded flavors
|
|
34
43
|
class Registry
|
|
35
44
|
@flavors = {}
|
|
36
45
|
|
|
37
46
|
class << self
|
|
38
|
-
|
|
47
|
+
# The registered flavors, always key-sorted and frozen. Sorted so
|
|
48
|
+
# no behavior can observe registration (= module load) order -
|
|
49
|
+
# hosts autoload flavor modules in any order, and order-dependent
|
|
50
|
+
# iteration once routed the same identifier to different flavors
|
|
51
|
+
# per process. Frozen so the raw table is not public mutable
|
|
52
|
+
# state; #register is the only mutator.
|
|
53
|
+
# @return [Hash{String => Module}]
|
|
54
|
+
def flavors
|
|
55
|
+
@flavors.sort.to_h.freeze
|
|
56
|
+
end
|
|
39
57
|
|
|
40
58
|
# Register a flavor with the registry
|
|
41
59
|
# @param name [String, Symbol] Flavor name (e.g., :iso, :iec)
|
|
@@ -44,6 +62,15 @@ module Pubid
|
|
|
44
62
|
@flavors[name.to_s.downcase] = flavor_module
|
|
45
63
|
end
|
|
46
64
|
|
|
65
|
+
# Remove a flavor registration. Tests that register probe flavors
|
|
66
|
+
# MUST unregister them: registry-driven specs enumerate every
|
|
67
|
+
# registered flavor, so a leaked probe fails them for everyone
|
|
68
|
+
# else in the process.
|
|
69
|
+
# @param name [String, Symbol] Flavor name
|
|
70
|
+
def unregister(name)
|
|
71
|
+
@flavors.delete(name.to_s.downcase)
|
|
72
|
+
end
|
|
73
|
+
|
|
47
74
|
# Get all registered flavor names
|
|
48
75
|
# @return [Array<String>] Array of flavor names
|
|
49
76
|
def flavor_names
|
|
@@ -57,30 +84,62 @@ module Pubid
|
|
|
57
84
|
@flavors[name.to_s.downcase]
|
|
58
85
|
end
|
|
59
86
|
|
|
87
|
+
# The canonical registered name of a module that may be registered
|
|
88
|
+
# under aliases (CenCenelec registers both cen_cenelec and cen).
|
|
89
|
+
# Canonical = the name the module's own prefix table is keyed by,
|
|
90
|
+
# so the answer cannot depend on registration or iteration order.
|
|
91
|
+
# @param flavor_module [Module]
|
|
92
|
+
# @return [String, nil]
|
|
93
|
+
def canonical_name(flavor_module)
|
|
94
|
+
if flavor_module.singleton_class.method_defined?(:prefix_flavor_key)
|
|
95
|
+
key = flavor_module.prefix_flavor_key.to_s
|
|
96
|
+
return key if @flavors[key] == flavor_module
|
|
97
|
+
end
|
|
98
|
+
@flavors.key(flavor_module)
|
|
99
|
+
end
|
|
100
|
+
|
|
60
101
|
# Check if a flavor is registered
|
|
61
102
|
# @param name [String, Symbol] Flavor name
|
|
62
103
|
# @return [Boolean]
|
|
63
104
|
def registered?(name)
|
|
64
105
|
@flavors.key?(name.to_s.downcase)
|
|
65
106
|
end
|
|
107
|
+
|
|
108
|
+
# Parse an identifier without naming its flavor.
|
|
109
|
+
#
|
|
110
|
+
# The pubid 1.x entry point, kept because consumers still call it —
|
|
111
|
+
# isodoc's +std_docid_semantic_parse+ among them. It delegates to
|
|
112
|
+
# {Pubid.parse}; it is not a second implementation.
|
|
113
|
+
#
|
|
114
|
+
# @param string [String] the identifier string
|
|
115
|
+
# @return [Pubid::Identifier]
|
|
116
|
+
# @raise [Parslet::ParseFailed] when no flavor can parse it
|
|
117
|
+
def parse(string, **opts)
|
|
118
|
+
Pubid.parse(string, **opts)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Build an identifier from its hash without naming its flavor.
|
|
122
|
+
# It delegates to {Pubid.from_hash}, as {parse} delegates to
|
|
123
|
+
# {Pubid.parse}.
|
|
124
|
+
#
|
|
125
|
+
# @param data [Hash] an identifier hash
|
|
126
|
+
# @return [Pubid::Identifier]
|
|
127
|
+
# @raise [Pubid::Errors::InvalidInputError] see {Pubid.from_hash}
|
|
128
|
+
def from_hash(data)
|
|
129
|
+
Pubid.from_hash(data)
|
|
130
|
+
end
|
|
66
131
|
end
|
|
67
132
|
end
|
|
68
133
|
|
|
69
|
-
# Canonical joint / co-publication leading tokens,
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
# so co-publication symmetry can never drift. Keyed by
|
|
134
|
+
# Canonical joint / co-publication leading tokens, sourced at boot from
|
|
135
|
+
# schema/core/joint_prefixes.yaml - the single source of truth. Injected
|
|
136
|
+
# symmetrically into every participating flavor's +prefixes+ (see
|
|
137
|
+
# {PrefixesSupport}) so co-publication symmetry can never drift. Keyed by
|
|
74
138
|
# {PrefixesSupport#prefix_flavor_key}.
|
|
75
|
-
JOINT_PREFIXES =
|
|
76
|
-
|
|
77
|
-
iec: ["ISO/IEC", "IEC/ISO", "ISO/IEC/IEEE"],
|
|
78
|
-
ieee: ["ISO/IEC/IEEE"],
|
|
79
|
-
ansi: ["ANSI/ASHRAE", "ANSI/AMCA"],
|
|
80
|
-
ashrae: ["ANSI/ASHRAE"],
|
|
81
|
-
amca: ["ANSI/AMCA"],
|
|
82
|
-
}.freeze
|
|
139
|
+
JOINT_PREFIXES = Schema::Loader.joint_prefixes_map
|
|
140
|
+
.transform_keys(&:to_sym).freeze
|
|
83
141
|
|
|
142
|
+
autoload :Errors, "pubid/errors"
|
|
84
143
|
autoload :Parser, "pubid/parser"
|
|
85
144
|
autoload :Components, "pubid/components"
|
|
86
145
|
autoload :BundledIdentifier, "pubid/bundled_identifier"
|
|
@@ -119,6 +178,7 @@ module Pubid
|
|
|
119
178
|
autoload :Csa, "pubid/csa"
|
|
120
179
|
autoload :Ecma, "pubid/ecma"
|
|
121
180
|
autoload :Etsi, "pubid/etsi"
|
|
181
|
+
autoload :Evs, "pubid/evs"
|
|
122
182
|
autoload :Gost, "pubid/gost"
|
|
123
183
|
autoload :Gb, "pubid/gb"
|
|
124
184
|
autoload :Idf, "pubid/idf"
|
|
@@ -161,17 +221,47 @@ module Pubid
|
|
|
161
221
|
|
|
162
222
|
# Unified parse entry point with auto-detection
|
|
163
223
|
#
|
|
224
|
+
# A human-readable string is routed to its owning flavor by leading prefix
|
|
225
|
+
# token, using the {prefix_flavors} table. This restores the pubid 1.x
|
|
226
|
+
# +Pubid::Registry.parse+ capability, which isodoc still calls; before it,
|
|
227
|
+
# this method raised +No flavor specified+ for every human-readable string,
|
|
228
|
+
# so a reference whose flavor the caller did not already know could not be
|
|
229
|
+
# parsed at all.
|
|
230
|
+
#
|
|
164
231
|
# @param string [String] The identifier string to parse
|
|
165
232
|
# @param format [Symbol] :auto, :human, :mr_string, or :urn
|
|
166
233
|
# @return [Identifier] The parsed identifier
|
|
234
|
+
# @raise [ArgumentError] for a non-String, or one over {MAX_INPUT_LENGTH}
|
|
235
|
+
# @raise [Parslet::ParseFailed] when no flavor can parse the string — the
|
|
236
|
+
# class every flavor's own +parse+ raises (see the parse-failure contract)
|
|
167
237
|
def self.parse(string, format: :auto)
|
|
168
|
-
|
|
238
|
+
unless string.is_a?(String)
|
|
239
|
+
raise Pubid::Errors::InvalidInputError,
|
|
240
|
+
Pubid::INPUT_NOT_A_STRING_MESSAGE
|
|
241
|
+
end
|
|
242
|
+
if string.length > MAX_INPUT_LENGTH
|
|
243
|
+
raise Pubid::Errors::InvalidInputError,
|
|
244
|
+
Pubid::INPUT_TOO_LONG_MESSAGE
|
|
245
|
+
end
|
|
169
246
|
|
|
170
247
|
format = FormatDetector.detect(string) if format == :auto
|
|
171
248
|
|
|
172
249
|
case format
|
|
173
250
|
when :mr_string
|
|
174
|
-
|
|
251
|
+
# The MR detector is a shape heuristic (`/\A[A-Z]{2,}[.-]/`), and some
|
|
252
|
+
# human-readable identifiers have that shape — "ITU-T G.711" is the
|
|
253
|
+
# standing example. Fall through to prefix routing when the MR parse
|
|
254
|
+
# fails, rather than reporting an MR error for a string that was never
|
|
255
|
+
# an MR string. A genuine MR failure still surfaces, from the second
|
|
256
|
+
# attempt, as Parslet::ParseFailed.
|
|
257
|
+
#
|
|
258
|
+
# Narrow, for the reason spelled out on {parse_by_prefix}: only a parse
|
|
259
|
+
# failure means "wrong shape". Anything else is a defect and propagates.
|
|
260
|
+
begin
|
|
261
|
+
Parsers::MrString.parse(string)
|
|
262
|
+
rescue Parslet::ParseFailed
|
|
263
|
+
parse_by_prefix(string)
|
|
264
|
+
end
|
|
175
265
|
when :urn
|
|
176
266
|
eager_load_flavors!
|
|
177
267
|
flavor = detect_flavor_from_urn(string)
|
|
@@ -182,18 +272,239 @@ module Pubid
|
|
|
182
272
|
end
|
|
183
273
|
|
|
184
274
|
urn_parser = flavor_module.const_get(:UrnParser)
|
|
185
|
-
urn_parser.parse(string)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
275
|
+
urn_parser.parse(string)
|
|
276
|
+
else
|
|
277
|
+
parse_by_prefix(string)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Build an identifier from its serialized hash, without naming its flavor —
|
|
282
|
+
# the hash counterpart of {parse}.
|
|
283
|
+
#
|
|
284
|
+
# The hash is what {Identifier#to_hash} emits, and what a relaton-data
|
|
285
|
+
# +index-v2.yaml+ row stores under +:id+. Its +_type+ key
|
|
286
|
+
# ("pubid:<flavor>:<type>") names the concrete class, so no prefix guessing
|
|
287
|
+
# is necessary. The class is resolved by {TypeResolver}, and the class's own
|
|
288
|
+
# +from_hash+ does the rest, nested identifiers of other flavors included.
|
|
289
|
+
#
|
|
290
|
+
# @example
|
|
291
|
+
# Pubid.from_hash("_type" => "pubid:iso:international-standard",
|
|
292
|
+
# "number" => "9001", "year" => "2015").to_s
|
|
293
|
+
# # => "ISO 9001:2015"
|
|
294
|
+
#
|
|
295
|
+
# @param data [Hash] an identifier hash. String and Symbol keys are both
|
|
296
|
+
# accepted.
|
|
297
|
+
# @return [Identifier] an instance of the class that +_type+ names
|
|
298
|
+
# @raise [Pubid::Errors::InvalidInputError] when +data+ is not a Hash, has no
|
|
299
|
+
# +_type+, or has a +_type+ that no registered flavor defines. These are
|
|
300
|
+
# the only failures it translates: once +_type+ names a class, an error
|
|
301
|
+
# from that class's own +from_hash+ (a malformed field, or a bad +_type+
|
|
302
|
+
# in a NESTED hash) propagates unchanged, so a flavor defect is not
|
|
303
|
+
# disguised as bad input.
|
|
304
|
+
def self.from_hash(data)
|
|
305
|
+
unless data.is_a?(Hash)
|
|
306
|
+
raise Pubid::Errors::InvalidInputError,
|
|
307
|
+
"identifier data must be a Hash, got #{data.class}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
identifier_class_for(data).from_hash(data)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# The concrete class that the +_type+ of +data+ names.
|
|
314
|
+
#
|
|
315
|
+
# @param data [Hash] an identifier hash
|
|
316
|
+
# @return [Class<Identifier>]
|
|
317
|
+
# @raise [Pubid::Errors::InvalidInputError] no +_type+, or an unknown one
|
|
318
|
+
# @api private
|
|
319
|
+
def self.identifier_class_for(data)
|
|
320
|
+
type = data["_type"] || data[:_type]
|
|
321
|
+
unless type
|
|
322
|
+
raise Pubid::Errors::InvalidInputError,
|
|
323
|
+
"identifier hash has no _type key: #{data.inspect[0, 200]}"
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
TypeResolver.resolve(type) ||
|
|
327
|
+
raise(Pubid::Errors::InvalidInputError,
|
|
328
|
+
"unknown identifier _type: #{type.inspect}")
|
|
329
|
+
end
|
|
330
|
+
private_class_method :identifier_class_for
|
|
331
|
+
|
|
332
|
+
# Route a human-readable identifier to its flavor by leading prefix token.
|
|
333
|
+
#
|
|
334
|
+
# Three passes, widening each time — the pubid 1.x +Registry.parse+ shape:
|
|
335
|
+
#
|
|
336
|
+
# 1. flavors owning the longest matching prefix ("ISO/IEC" before "ISO")
|
|
337
|
+
# 2. every other flavor, in registry order
|
|
338
|
+
#
|
|
339
|
+
# A longest-match-first order matters because prefixes nest: "ISO/IEC 2131"
|
|
340
|
+
# must not be handed to the flavor that merely owns "ISO".
|
|
341
|
+
#
|
|
342
|
+
# @param string [String] a human-readable identifier
|
|
343
|
+
# @return [Identifier]
|
|
344
|
+
# @raise [Parslet::ParseFailed] when no flavor accepts the string
|
|
345
|
+
# @api private
|
|
346
|
+
def self.parse_by_prefix(string)
|
|
347
|
+
eager_load_flavors!
|
|
348
|
+
|
|
349
|
+
first_error = nil
|
|
350
|
+
fallback = nil
|
|
351
|
+
|
|
352
|
+
prefix_owners = candidates_by_prefix(string)
|
|
353
|
+
(prefix_owners + other_candidates(prefix_owners)).each do |mod|
|
|
354
|
+
begin
|
|
355
|
+
parsed = mod.parse(string)
|
|
356
|
+
rescue Parslet::ParseFailed => e
|
|
357
|
+
# ONLY a parse failure means "not this flavor's identifier". Every
|
|
358
|
+
# other exception is a defect in that flavor and must propagate.
|
|
359
|
+
#
|
|
360
|
+
# A blanket `rescue StandardError` here would swallow exactly the
|
|
361
|
+
# `ArgumentError: unknown attribute…` that the constructor contract on
|
|
362
|
+
# `Identifier` now raises — the error that turned three silent
|
|
363
|
+
# data-loss bugs (CIE `s_prefix`, IEEE `adopted_identifier`,
|
|
364
|
+
# IEEE `csa_identifier`) into visible ones. Catching it one layer up
|
|
365
|
+
# would hand it straight back its disguise: a real builder bug in any
|
|
366
|
+
# flavor would read as "this string is not an identifier".
|
|
367
|
+
first_error ||= e
|
|
368
|
+
next
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# An exact round trip is the strongest evidence the string belongs to
|
|
372
|
+
# this flavor. It matters because some grammars accept anything by
|
|
373
|
+
# design — adobe and iana take any slug — so without it the first such
|
|
374
|
+
# flavor in registry order claims every unclaimed string and invents
|
|
375
|
+
# structure that was not in the input. Same test isodoc applies before
|
|
376
|
+
# it will annotate a parsed id.
|
|
377
|
+
return parsed if parsed.to_s == string
|
|
378
|
+
|
|
379
|
+
# A non-exact parse is still usable — several flavors legitimately
|
|
380
|
+
# normalise on render, so the round trip is a preference, not a
|
|
381
|
+
# requirement (OGC prints `06-121r9` for `OGC 06-121r9`, and 32 ASHRAE /
|
|
382
|
+
# ASME / CSA / IEEE identifiers route only this way). What it must NOT
|
|
383
|
+
# come from is a flavor that accepts anything: that is how `iec.60050`
|
|
384
|
+
# came back as `IANA iec.60050`. Reporting that no flavor could be
|
|
385
|
+
# determined beats inventing one.
|
|
386
|
+
fallback ||= parsed unless accepts_anything?(mod)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
return fallback if fallback
|
|
390
|
+
raise first_error if first_error
|
|
391
|
+
|
|
392
|
+
raise Parslet::ParseFailed,
|
|
393
|
+
"no registered flavor could parse #{string.inspect}"
|
|
394
|
+
end
|
|
395
|
+
private_class_method :parse_by_prefix
|
|
396
|
+
|
|
397
|
+
# Flavor modules that own a prefix +string+ starts with, longest prefix
|
|
398
|
+
# first. Deduplicated by module identity, so an alias registration is not
|
|
399
|
+
# tried twice.
|
|
400
|
+
#
|
|
401
|
+
# These are the flavors with an actual claim on the string; a parse from one
|
|
402
|
+
# of them is trusted even when the flavor normalises on render.
|
|
403
|
+
#
|
|
404
|
+
# @param string [String] a human-readable identifier
|
|
405
|
+
# @return [Array<Module>]
|
|
406
|
+
# @api private
|
|
407
|
+
def self.candidates_by_prefix(string)
|
|
408
|
+
ordered = []
|
|
409
|
+
routing_table.each do |prefix, modules|
|
|
410
|
+
ordered.concat(modules) if prefix_match?(string, prefix)
|
|
411
|
+
end
|
|
412
|
+
ordered.compact.uniq
|
|
413
|
+
end
|
|
414
|
+
private_class_method :candidates_by_prefix
|
|
415
|
+
|
|
416
|
+
# Every other registered flavor, for the exhaustive second pass.
|
|
417
|
+
#
|
|
418
|
+
# @param exclude [Array<Module>] modules already tried
|
|
419
|
+
# @return [Array<Module>]
|
|
420
|
+
# @api private
|
|
421
|
+
def self.other_candidates(exclude)
|
|
422
|
+
ordered = []
|
|
423
|
+
each_prefix_flavor_module { |mod| ordered << mod }
|
|
424
|
+
ordered.compact.uniq - exclude
|
|
425
|
+
end
|
|
426
|
+
private_class_method :other_candidates
|
|
427
|
+
|
|
428
|
+
# A meaningless string no real identifier scheme should claim. Used to find
|
|
429
|
+
# the flavors whose grammar accepts anything.
|
|
430
|
+
ROUTING_PROBE = "zzqx-nonsense-9714"
|
|
431
|
+
private_constant :ROUTING_PROBE
|
|
432
|
+
|
|
433
|
+
# True when +mod+'s grammar accepts an arbitrary slug.
|
|
434
|
+
#
|
|
435
|
+
# `adobe` and `iana` do, by design — their identifiers genuinely are free
|
|
436
|
+
# text. That makes them unsafe as a fallback: whichever came first in
|
|
437
|
+
# registry order would claim every string no other flavor matched, and
|
|
438
|
+
# answer with structure the input never had.
|
|
439
|
+
#
|
|
440
|
+
# Detected by probing rather than by a hardcoded list, so a flavor that
|
|
441
|
+
# acquires (or loses) an anything-goes grammar is classified correctly
|
|
442
|
+
# without anyone remembering to edit a constant. Computed once per module.
|
|
443
|
+
#
|
|
444
|
+
# @param mod [Module] a flavor module
|
|
445
|
+
# @return [Boolean]
|
|
446
|
+
# @api private
|
|
447
|
+
def self.accepts_anything?(mod)
|
|
448
|
+
@accepts_anything ||= {}
|
|
449
|
+
return @accepts_anything[mod] if @accepts_anything.key?(mod)
|
|
450
|
+
|
|
451
|
+
@accepts_anything[mod] =
|
|
452
|
+
begin
|
|
453
|
+
mod.parse(ROUTING_PROBE)
|
|
454
|
+
true
|
|
455
|
+
rescue StandardError, Parslet::ParseFailed
|
|
456
|
+
false
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
private_class_method :accepts_anything?
|
|
460
|
+
|
|
461
|
+
# Prefix => flavor modules, longest prefix first, built once.
|
|
462
|
+
#
|
|
463
|
+
# {prefix_flavors} rebuilds its index on every call, iterating every
|
|
464
|
+
# registered flavor; routing consulted it twice per parse. The registry is
|
|
465
|
+
# static once +eager_load_flavors!+ has run, so the table is memoised here
|
|
466
|
+
# rather than making the public method's cost implicit.
|
|
467
|
+
#
|
|
468
|
+
# The memo is never invalidated, which is safe for every flavor shipped in
|
|
469
|
+
# this gem — they are all +Pubid+-namespace constants that
|
|
470
|
+
# +eager_load_flavors!+ loads before the first parse. A flavor registered
|
|
471
|
+
# from OUTSIDE that namespace after the first {parse} call would miss its
|
|
472
|
+
# prefix priority (it is still tried in the exhaustive second pass, so it is
|
|
473
|
+
# reachable, just not preferred). Bust the memo in +Registry.register+ if
|
|
474
|
+
# out-of-namespace flavors ever become a supported extension point.
|
|
475
|
+
#
|
|
476
|
+
# @return [Hash{String => Array<Module>}]
|
|
477
|
+
# @api private
|
|
478
|
+
def self.routing_table
|
|
479
|
+
@routing_table ||= begin
|
|
480
|
+
index = prefix_flavors
|
|
481
|
+
index.keys
|
|
482
|
+
.sort_by { |prefix| -prefix.length }
|
|
483
|
+
.to_h { |prefix| [prefix, index[prefix].map { |key| Registry.get(key) }] }
|
|
190
484
|
end
|
|
191
485
|
end
|
|
486
|
+
private_class_method :routing_table
|
|
487
|
+
|
|
488
|
+
# True when +string+ starts with +prefix+ at a token boundary, so "ISO" does
|
|
489
|
+
# not claim "ISOFIX" and "BS" does not claim "BSI".
|
|
490
|
+
# @api private
|
|
491
|
+
def self.prefix_match?(string, prefix)
|
|
492
|
+
return false unless string.start_with?(prefix)
|
|
493
|
+
|
|
494
|
+
rest = string[prefix.length]
|
|
495
|
+
rest.nil? || !/[A-Za-z0-9]/.match?(rest)
|
|
496
|
+
end
|
|
497
|
+
private_class_method :prefix_match?
|
|
192
498
|
|
|
193
499
|
def self.detect_flavor_from_urn(urn)
|
|
194
500
|
# urn:iso:std:... → "iso"
|
|
195
501
|
# urn:iec:std:... → "iec"
|
|
502
|
+
# urn:mrn:iala:pub:... → "iala": MRN URNs carry an assigning authority
|
|
503
|
+
# where ISO-style URNs carry their namespace, so routing follows the
|
|
504
|
+
# authority (works for any future MRN assignee, not only IALA).
|
|
196
505
|
parts = urn.downcase.split(":")
|
|
506
|
+
return parts[2] if parts[1] == "mrn"
|
|
507
|
+
|
|
197
508
|
parts[1] # The namespace part after "urn"
|
|
198
509
|
end
|
|
199
510
|
|
|
@@ -267,4 +578,71 @@ module Pubid
|
|
|
267
578
|
yield mod
|
|
268
579
|
end
|
|
269
580
|
end
|
|
581
|
+
|
|
582
|
+
# Typed-stage lookup by abbreviation, across flavors.
|
|
583
|
+
#
|
|
584
|
+
# Each flavor module memoises +all_typed_stages+ over its own +Identifiers+
|
|
585
|
+
# namespace, so an abbreviation only one flavor uses is invisible from any
|
|
586
|
+
# other: +Pubid::Iso.locate_stage("ADTS")+ is nil while
|
|
587
|
+
# +Pubid::Iec.locate_stage("ADTS")+ finds it. A consumer that holds an
|
|
588
|
+
# abbreviation but not the owning flavor had nowhere to ask.
|
|
589
|
+
#
|
|
590
|
+
# @param abbr [String, Symbol] the stage abbreviation (case-insensitive)
|
|
591
|
+
# @param flavor [Symbol, String, nil] search this flavor only; nil searches
|
|
592
|
+
# every registered flavor and returns the first match
|
|
593
|
+
# @return [Object, nil] the flavor's typed-stage object, or nil. The class is
|
|
594
|
+
# flavor-specific: most return {Pubid::Components::TypedStage}, but IEEE
|
|
595
|
+
# has its own +Ieee::Components::TypedStage+, which is not a subclass.
|
|
596
|
+
# @raise [ArgumentError] if +flavor+ names a flavor that is not registered
|
|
597
|
+
def self.locate_stage(abbr, flavor: nil)
|
|
598
|
+
return locate_stage_in(Registry.get(flavor), abbr, flavor) if flavor
|
|
599
|
+
|
|
600
|
+
each_stage_flavor_module do |mod|
|
|
601
|
+
stage = mod.locate_stage(abbr)
|
|
602
|
+
return stage if stage
|
|
603
|
+
end
|
|
604
|
+
nil
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Every flavor that knows +abbr+ — the diagnostic companion to
|
|
608
|
+
# {locate_stage}, for telling a consumer which module owns a stage.
|
|
609
|
+
#
|
|
610
|
+
# @param abbr [String, Symbol] the stage abbreviation (case-insensitive)
|
|
611
|
+
# @return [Array<Symbol>] registered flavor keys, sorted
|
|
612
|
+
def self.locate_stage_flavors(abbr)
|
|
613
|
+
found = []
|
|
614
|
+
each_stage_flavor_module do |mod, flavor_name|
|
|
615
|
+
found << flavor_name.to_sym if mod.locate_stage(abbr)
|
|
616
|
+
end
|
|
617
|
+
found.sort
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Yields each unique flavor module that implements +locate_stage+, exactly
|
|
621
|
+
# once. Three registered flavors (adobe, easc, gost) define no typed stages
|
|
622
|
+
# at all, so a cross-flavor sweep must skip them rather than raise on the
|
|
623
|
+
# first one it reaches; alias registrations are collapsed by module identity,
|
|
624
|
+
# as in {each_prefix_flavor_module}.
|
|
625
|
+
#
|
|
626
|
+
# @yieldparam mod [Module] a flavor module
|
|
627
|
+
# @yieldparam flavor_name [String] the registry key it was reached under
|
|
628
|
+
def self.each_stage_flavor_module
|
|
629
|
+
eager_load_flavors!
|
|
630
|
+
seen = {}
|
|
631
|
+
Registry.flavor_names.each do |flavor_name|
|
|
632
|
+
mod = Registry.get(flavor_name)
|
|
633
|
+
next if seen.key?(mod) || !mod.respond_to?(:locate_stage)
|
|
634
|
+
|
|
635
|
+
seen[mod] = true
|
|
636
|
+
yield mod, flavor_name
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# @api private
|
|
641
|
+
def self.locate_stage_in(mod, abbr, flavor)
|
|
642
|
+
raise ArgumentError, "unknown flavor: #{flavor.inspect}" unless mod
|
|
643
|
+
return nil unless mod.respond_to?(:locate_stage)
|
|
644
|
+
|
|
645
|
+
mod.locate_stage(abbr)
|
|
646
|
+
end
|
|
647
|
+
private_class_method :locate_stage_in
|
|
270
648
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
namespace :conformance do
|
|
7
|
+
desc "Generate the neutral corpus from ground-truth fixtures"
|
|
8
|
+
task :generate, [:flavor] do |_t, args|
|
|
9
|
+
require "pubid"
|
|
10
|
+
require "pubid/conformance"
|
|
11
|
+
flavor = args[:flavor] || "iso"
|
|
12
|
+
tests_repo = ENV.fetch("PUBID_TESTSUITE_PATH",
|
|
13
|
+
File.expand_path("../../../pubid-testsuite",
|
|
14
|
+
__dir__))
|
|
15
|
+
output_dir = File.join(tests_repo, "tests", flavor)
|
|
16
|
+
FileUtils.mkdir_p(output_dir)
|
|
17
|
+
results = Pubid::Conformance::Generator.new(flavor)
|
|
18
|
+
.generate(output_dir: output_dir)
|
|
19
|
+
stats = results
|
|
20
|
+
puts "#{flavor}: #{stats[:files]} files, #{stats[:cases]} cases, " \
|
|
21
|
+
"#{stats[:aliases]} aliases, #{stats[:duplicates]} duplicates, " \
|
|
22
|
+
"#{stats[:reclassify]} reclassify, #{stats[:negative]} negative, " \
|
|
23
|
+
"#{stats[:debt]} debt, #{stats[:roundtrip_failures]} rt-failures"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc "Run the neutral corpus against this implementation"
|
|
27
|
+
task :run do
|
|
28
|
+
require "pubid"
|
|
29
|
+
require "pubid/conformance"
|
|
30
|
+
failures = Pubid::Conformance::Runner.new.run.flatten
|
|
31
|
+
puts "TOTAL failures: #{failures.size}"
|
|
32
|
+
exit(failures.empty? ? 0 : 1)
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/tasks/docs.rake
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
# frozen_string_literal
|
|
2
|
-
|
|
3
|
-
require_relative "../../lib/pubid/core/pattern_doc_generator"
|
|
1
|
+
# frozen_string_literal: true
|
|
4
2
|
|
|
5
3
|
namespace :docs do
|
|
6
4
|
desc "Generate identifier pattern reference docs"
|
|
7
5
|
task :patterns do
|
|
6
|
+
require "pubid"
|
|
7
|
+
|
|
8
8
|
output_dir = File.join(__dir__, "..", "..", "docs", "identifier-patterns")
|
|
9
9
|
FileUtils.mkdir_p(output_dir)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
lib_root = File.join(__dir__, "..", "..", "lib", "pubid")
|
|
12
|
+
# The registry is the authoritative flavor list. This replaces a
|
|
13
|
+
# filesystem glob over lib/pubid/* directories, whose reject list kept
|
|
14
|
+
# going stale and swept internal modules (parsers, renderers, lutaml,
|
|
15
|
+
# builder, utils, ...) in as "flavors". The module-derived directory
|
|
16
|
+
# (CenCenelec -> cen_cenelec, W3c -> w3c) must exist on disk.
|
|
17
|
+
Pubid.eager_load_flavors!
|
|
18
|
+
flavors = Pubid::Registry.flavor_names.filter_map do |name|
|
|
19
|
+
mod = Pubid::Registry.get(name)
|
|
20
|
+
dir = mod.name.split("::").last
|
|
21
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
22
|
+
dir if File.directory?(File.join(lib_root, dir))
|
|
23
|
+
end.uniq.sort
|
|
22
24
|
|
|
23
25
|
flavors.each do |flavor|
|
|
24
26
|
puts "Generating docs for #{flavor}..."
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
# Extracts scheme declarations from the live model into schema/{flavor}.yaml.
|
|
8
|
+
# Seed tooling for TODO.restructure/01-02; TODO.restructure/07 inverts the
|
|
9
|
+
# direction (the identifier classes read these files instead of carrying the
|
|
10
|
+
# data in constants and class methods).
|
|
11
|
+
class SchemeExtractor
|
|
12
|
+
def initialize(flavor)
|
|
13
|
+
require "pubid"
|
|
14
|
+
require "pubid/export"
|
|
15
|
+
|
|
16
|
+
@flavor = flavor.to_s
|
|
17
|
+
Pubid.eager_load_flavors!
|
|
18
|
+
@flavor_module = Pubid::Registry.get(@flavor)
|
|
19
|
+
raise ArgumentError, "Unknown flavor: #{@flavor}" unless @flavor_module
|
|
20
|
+
|
|
21
|
+
exporter = Pubid::Export::FlavorExporter.new(@flavor.to_sym)
|
|
22
|
+
@export = JSON.parse(JSON.generate(exporter.export.to_h))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def payload
|
|
26
|
+
types = @export["identifier_types"].map { |type| type_entry(type) }
|
|
27
|
+
{
|
|
28
|
+
"flavor" => @flavor,
|
|
29
|
+
"schema_version" => "1.0.0",
|
|
30
|
+
"prefixes" => own_prefixes,
|
|
31
|
+
"identifier_types" => types,
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def write(output_dir)
|
|
36
|
+
FileUtils.mkdir_p(output_dir)
|
|
37
|
+
path = File.join(output_dir, "#{@flavor}.yaml")
|
|
38
|
+
File.write(path, header + YAML.dump(payload))
|
|
39
|
+
puts "Extracted #{payload['identifier_types'].size} types to #{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def own_prefixes
|
|
45
|
+
if @flavor_module.const_defined?(:PREFIXES, false)
|
|
46
|
+
@flavor_module.const_get(:PREFIXES).to_a
|
|
47
|
+
else
|
|
48
|
+
@flavor_module.prefixes
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def type_entry(type)
|
|
53
|
+
stages = (type["typed_stages"] || []).map { |stage| stage_entry(stage) }
|
|
54
|
+
{
|
|
55
|
+
"key" => type["key"],
|
|
56
|
+
"title" => type["title"],
|
|
57
|
+
"short" => type["short"],
|
|
58
|
+
"abbr" => type["abbr"],
|
|
59
|
+
"typed_stages" => stages,
|
|
60
|
+
}.compact
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def stage_entry(stage)
|
|
64
|
+
{
|
|
65
|
+
"stage_code" => stage["stage_code"],
|
|
66
|
+
"type_code" => stage["type_code"],
|
|
67
|
+
"abbr" => stage["abbr"],
|
|
68
|
+
"name" => stage["name"],
|
|
69
|
+
"harmonized_stages" => stage["harmonized_stages"],
|
|
70
|
+
}.compact
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def header
|
|
74
|
+
[
|
|
75
|
+
"# Generated by rake schema:extract[#{@flavor}] - SSOT seed.",
|
|
76
|
+
"# Joint prefixes live in schema/core/joint_prefixes.yaml.\n",
|
|
77
|
+
].join("\n")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
namespace :schema do
|
|
82
|
+
desc "Extract scheme declarations into schema/{flavor}.yaml"
|
|
83
|
+
task :extract, [:flavor] do |_t, args|
|
|
84
|
+
flavor = args[:flavor] || "iso"
|
|
85
|
+
output_dir = File.expand_path("../../schema", __dir__)
|
|
86
|
+
SchemeExtractor.new(flavor).write(output_dir)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Canonical joint / co-publication leading tokens, mirroring
|
|
2
|
+
# Pubid::JOINT_PREFIXES (lib/pubid.rb). Keyed by flavor; merged into each
|
|
3
|
+
# participating flavor's prefixes at load time so co-publication symmetry can
|
|
4
|
+
# never drift (an ISO/IEC token appears in both the iso and iec lists).
|
|
5
|
+
# TODO.restructure/05 makes this file the only home for joint tokens.
|
|
6
|
+
schema_version: 1.0.0
|
|
7
|
+
joint_prefixes:
|
|
8
|
+
iso:
|
|
9
|
+
- ISO/IEC
|
|
10
|
+
- IEC/ISO
|
|
11
|
+
- ISO/IEC/IEEE
|
|
12
|
+
iec:
|
|
13
|
+
- ISO/IEC
|
|
14
|
+
- IEC/ISO
|
|
15
|
+
- ISO/IEC/IEEE
|
|
16
|
+
ieee:
|
|
17
|
+
- ISO/IEC/IEEE
|
|
18
|
+
ansi:
|
|
19
|
+
- ANSI/ASHRAE
|
|
20
|
+
- ANSI/AMCA
|
|
21
|
+
ashrae:
|
|
22
|
+
- ANSI/ASHRAE
|
|
23
|
+
amca:
|
|
24
|
+
- ANSI/AMCA
|