pubid 2.0.0.pre.alpha.44 → 2.0.0.pre.alpha.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 217d5576587e97055e8ad7cb387e62925495649c4f2d4193692e8e727fac07cf
4
- data.tar.gz: a923fa20acab08537ed36a7ce823425fd130903a5a351280a212fb8db577ccae
3
+ metadata.gz: 8e59d6b8d85efbedab5ee3e65fb772d579e53b08df3a5b1fcea783c9e70c005e
4
+ data.tar.gz: 8a757f8f946b2998e984ed915bd60896c14a93f4bbce4368810faef5f70b13e6
5
5
  SHA512:
6
- metadata.gz: 4cbd63b039f15863cdc92000e6a417faad9e050d31194026c757e730bc94789ee050fff50039a9956573425bd93090eb0c9cd90b2753062cd3395e6ea23bd3a9
7
- data.tar.gz: d65ce7950395d4ca5277f2396795c88197eb3aed72228ac0d3a55f87282b14c6b5cc233a4e9332cae1637f36d253dce5db63b3e5ac7e0b7822f60ba20ca0ae15
6
+ metadata.gz: 5c03453929b1941309d16b6b87c8bee8dc54594c80a42336b1af5a986197ec6a5ab253451fed9198a67d00337149577d6cae57ed9e727f6a0a22fc26ac42480e
7
+ data.tar.gz: ae7af0fd7a05bb2879d50107ee5d3c1f617e26d2758f0a9a75f28b9ccb124cb914478fe88ec2a3ec64018ab7f8fc391b24e39ab2bfa5905c80fc4e965e6b7d4a
@@ -195,7 +195,21 @@ module Pubid
195
195
  # @return [Pubid::Components::TypedStage, nil] the matching typed stage
196
196
  def self.locate_stage(abbr)
197
197
  abbr_str = abbr.to_s.upcase
198
- all_typed_stages.find { |s| s.abbr.any? { |a| a.to_s.upcase == abbr_str } }
198
+ all_typed_stages.find { |s| s.abbr.any? { |a| a.to_s.upcase == abbr_str } } ||
199
+ # A harmonized stage code ("43.20") names the stage the same way an
200
+ # abbreviation does; consumers query by it (pubid#251), so an
201
+ # unmatched abbreviation falls through to the code tables.
202
+ locate_stage_by_harmonized_code(abbr)
203
+ end
204
+
205
+ # Lookup: harmonized stage code -> typed stage ("43.20" -> the FV EN
206
+ # entry). The codes are the CEN/CENELEC lifecycle ladder; every code a
207
+ # consumer can query must resolve (pubid#251).
208
+ # @param code [String, Symbol] the harmonized stage code to find
209
+ # @return [Pubid::Components::TypedStage, nil] the matching typed stage
210
+ def self.locate_stage_by_harmonized_code(code)
211
+ code_str = code.to_s
212
+ all_typed_stages.find { |s| s.harmonized_stages.to_a.include?(code_str) }
199
213
  end
200
214
 
201
215
  # Lookup: typed-stage code -> typed stage ("pren" -> the prEN entry).
@@ -142,16 +142,31 @@ module Pubid
142
142
  elsif TYPED_STAGE_REVERSE_MAP.key?(parts.first&.upcase) ||
143
143
  (parts.first&.match?(/^[A-Za-z]+\.\d+$/) &&
144
144
  TYPED_STAGE_REVERSE_MAP.key?(parts.first.upcase.split(".").first))
145
- stage_abbr = parts.shift.upcase
146
- # Check for iteration (WD.2 format)
147
- if stage_abbr.include?(".")
148
- abbr_part, iteration_part = stage_abbr.split(".")
149
- stage_code = TYPED_STAGE_REVERSE_MAP[abbr_part]
150
- stage_from_abbr = stage_code
151
- stage_iteration = iteration_part.to_i
152
- else
153
- stage_code = TYPED_STAGE_REVERSE_MAP[stage_abbr]
154
- stage_from_abbr = stage_code
145
+ stage_abbr = parts.first.upcase
146
+ # The family comes from the abbreviation without its iteration
147
+ # ("FDAM.3" is still the amd family).
148
+ family = Pubid::Iso.locate_stage(stage_abbr.split(".").first)&.type_code&.to_s
149
+ # A supplement-family stage directly before its own supplement
150
+ # type token ("...:FDAM:amd:1:v1") names the SUPPLEMENT's stage:
151
+ # consuming it as the base's stage builds the base as a base-less
152
+ # supplement, which cannot render.
153
+ supplement_follows =
154
+ %w[amd cor suppl add].include?(family) &&
155
+ parts.drop(1).any? do |token|
156
+ family == SUPPLEMENT_TYPE_MAP[token.to_s.downcase]&.to_s
157
+ end
158
+ unless supplement_follows
159
+ parts.shift
160
+ # Check for iteration (WD.2 format)
161
+ if stage_abbr.include?(".")
162
+ abbr_part, iteration_part = stage_abbr.split(".")
163
+ stage_code = TYPED_STAGE_REVERSE_MAP[abbr_part]
164
+ stage_from_abbr = stage_code
165
+ stage_iteration = iteration_part.to_i
166
+ else
167
+ stage_code = TYPED_STAGE_REVERSE_MAP[stage_abbr]
168
+ stage_from_abbr = stage_code
169
+ end
155
170
  end
156
171
  end
157
172
 
@@ -397,6 +412,16 @@ module Pubid
397
412
  # Build supplements recursively (forward order: first supplement wraps base,
398
413
  # second wraps first, etc. -- matching URN left-to-right semantics)
399
414
  supplements.each do |supp|
415
+ # A bare language token with no supplement identity names the BASE
416
+ # document: wrapping it would build a supplement with no type,
417
+ # number or base attribute, and a plain standard drops its base —
418
+ # the whole document was lost, rendering "ISO (FR)".
419
+ if supp[:type].nil? && supp[:stage].nil? && supp[:number].nil? &&
420
+ supp[:year].nil? && supp[:languages]
421
+ base_hash[:languages] = supp[:languages].join("/")
422
+ next
423
+ end
424
+
400
425
  supp_hash = {}
401
426
 
402
427
  if supp[:stage]
@@ -72,3 +72,11 @@ Read this before you change `lib/pubid/nist/` or `spec/pubid/nist/`. The root
72
72
  (`"800-38.A.supp.add."` splits with a trailing `""` in JS but not in
73
73
  Ruby; keeping it appends a stray `.`). Locked by
74
74
  `spec/pubid/nist/urn_parser_spec.rb`.
75
+
76
+ - **FIPS MR keeps the parsed publisher.** `FederalInformationProcessingStandards`
77
+ sets `default_publisher` to `""`, and its `to_mr_style` once started from
78
+ `series_code` alone, so `NIST.FIPS.197-upd1.ipd` rendered `FIPS.197-upd1.ipd`
79
+ — not the DOI suffix NIST publishes. It now mirrors its own
80
+ `to_short_style`: the publisher prefixes only when the input carried one
81
+ (`NIST.`/`NBS.`), and a bare `FIPS 140-3` still renders `FIPS.140-3`.
82
+ Locked by `spec/pubid/nist/identifiers/fips_spec.rb`.
@@ -37,11 +37,12 @@ module Pubid
37
37
  ""
38
38
  end
39
39
 
40
- # Override to_mr_style to not add "NIST" prefix for FIPS
40
+ # Override to_mr_style so FIPS adds no default "NIST" prefix
41
41
  def to_mr_style
42
- # "FIPS.46e1977" (machine-readable with dots, no publisher prefix)
42
+ # "NIST.FIPS.197" keeps a parsed publisher (the DOI suffix NIST
43
+ # publishes); "FIPS.46e1977" has none and invents none.
43
44
  # Note: edition is appended directly to number without dot for FIPS
44
- result = series_code
45
+ result = [publisher, series_code].compact.join(".")
45
46
  result += ".#{number}" if number
46
47
 
47
48
  # FIPS uses dash notation for parts; render here and skip the generic
data/lib/pubid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pubid
4
- VERSION = "2.0.0.pre.alpha.44"
4
+ VERSION = "2.0.0.pre.alpha.46"
5
5
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+
5
+ namespace :benchmark do
6
+ desc "Parse throughput per flavor over the testsuite corpus " \
7
+ "(PUBID_TESTSUITE_PATH=<dir>, FLAVOR=ieee,iso, LIMIT=N)"
8
+ task :corpus do
9
+ require "pubid"
10
+
11
+ corpus_dir = ENV["PUBID_TESTSUITE_PATH"] ||
12
+ File.expand_path("../../../pubid-testsuite/tests", __dir__)
13
+ unless Dir.exist?(corpus_dir)
14
+ abort "corpus not found at #{corpus_dir} — set PUBID_TESTSUITE_PATH " \
15
+ "to a pubid-testsuite checkout's tests/ directory"
16
+ end
17
+
18
+ limit = ENV["LIMIT"]&.to_i
19
+ dir_to_flavor = { "tgpp" => "3gpp" }
20
+ flavors = if ENV["FLAVOR"]
21
+ ENV["FLAVOR"].split(",").map(&:strip)
22
+ else
23
+ Dir.children(corpus_dir)
24
+ .select { |name| File.directory?(File.join(corpus_dir, name)) && !name.start_with?("_") }
25
+ .sort
26
+ end
27
+
28
+ Pubid.eager_load_flavors!
29
+
30
+ rows = flavors.filter_map do |dir_name|
31
+ flavor = (dir_to_flavor[dir_name] || dir_name).to_sym
32
+ mod = Pubid::Registry.get(flavor)
33
+ next unless mod
34
+
35
+ cases = Pubid::Conformance::Corpus.cases(dir_name, corpus_dir)
36
+ inputs = cases.filter_map { |c| c.representations&.human || c.input }
37
+ inputs = inputs.first(limit) if limit && limit.positive?
38
+ next if inputs.empty?
39
+
40
+ failures = 0
41
+ elapsed = ::Benchmark.realtime do
42
+ inputs.each do |input|
43
+ mod.parse(input)
44
+ rescue StandardError
45
+ failures += 1
46
+ end
47
+ end
48
+ rate = elapsed.zero? ? 0.0 : inputs.size / elapsed
49
+ { flavor: dir_name, cases: inputs.size, failures: failures,
50
+ seconds: elapsed, rate: rate }
51
+ end
52
+
53
+ rows.sort_by! { |row| -row[:rate] }
54
+ total_cases = rows.sum { |row| row[:cases] }
55
+ total_seconds = rows.sum { |row| row[:seconds] }
56
+
57
+ puts format("%-14s %8s %8s %10s %12s", "flavor", "cases", "failed", "seconds", "parses/sec")
58
+ rows.each do |row|
59
+ puts format("%-14s %8d %8d %10.3f %12.1f", row[:flavor], row[:cases],
60
+ row[:failures], row[:seconds], row[:rate])
61
+ end
62
+ puts format("%-14s %8d %8s %10.3f %12.1f", "TOTAL", total_cases, "",
63
+ total_seconds, total_seconds.zero? ? 0.0 : total_cases / total_seconds)
64
+ puts
65
+ puts "ruby #{RUBY_VERSION}; parsanol #{Gem.loaded_specs['parsanol']&.version}; " \
66
+ "pubid #{Pubid::VERSION}"
67
+ end
68
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.alpha.44
4
+ version: 2.0.0.pre.alpha.46
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-20 00:00:00.000000000 Z
11
+ date: 2026-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -1047,6 +1047,7 @@ files:
1047
1047
  - lib/pubid/xsf/renderer.rb
1048
1048
  - lib/pubid/xsf/urn_generator.rb
1049
1049
  - lib/pubid/xsf/urn_parser.rb
1050
+ - lib/tasks/benchmark.rake
1050
1051
  - lib/tasks/conformance.rake
1051
1052
  - lib/tasks/docs.rake
1052
1053
  - lib/tasks/export.rake