module RelatonItu class Pubid class Parser < Parslet::Parser rule(:dash) { str("-") } rule(:dot) { str(".") } rule(:dot?) { dot.maybe } rule(:separator) { match['\s-'] } rule(:space) { match("\s") } rule(:num) { match["0-9"] } rule(:prefix) { str("ITU").as(:prefix) } rule(:sector) { separator >> match("[A-Z]").as(:sector) } rule(:type) { separator >> str("REC").as(:type) } rule(:type?) { type.maybe } rule(:code) { separator >> (match["A-Z0-9"].repeat(1) >> match["[:alnum:]/.-"].repeat).as(:code) } rule(:year) { (match["12"] >> num.repeat(3, 3)).as(:year) } rule(:month1) { num.repeat(2, 2).as(:month) } rule(:date1) { str(" (") >> (month1 >> str("/")).maybe >> year >> str(")") } rule(:month2) { match["IVX"].repeat(1, 3).as(:month) } rule(:date2) { str(" - ") >> num.repeat(2, 2) >> dot >> month2 >> dot >> year } rule(:date) { date1 | date2 } rule(:date?) { date.maybe } rule(:amd) { space >> (str("Amd") | str("Amendment")) >> dot? >> space >> num.repeat(1, 2).as(:amd) } rule(:amd?) { amd.maybe } rule(:sup) { space >> str("Suppl") >> dot? >> space >> num.repeat(1, 2).as(:suppl) } rule(:sup?) { sup.maybe } rule(:annex) { space >> str("Annex") >> space >> match["[:alnum:]"].repeat(1, 2).as(:annex) } rule(:annex?) { annex.maybe } rule(:itu_pubid) { prefix >> sector >> type? >> code >> sup? >> annex? >> date? >> amd? >> any.repeat } root(:itu_pubid) end attr_accessor :prefix, :sector, :type, :code, :suppl, :annex, :year, :month, :amd # # Create a new ITU publication identifier. # # @param [String] prefix # @param [String] sector # @param [String, nil] type # @param [String] code # @param [String, nil] suppl number # @param [String, nil] year # @param [String, nil] month # @param [String, nil] amd amendment number # def initialize(prefix:, sector:, code:, **args) @prefix = prefix @sector = sector @type = args[:type] @code, year, month = date_from_code code @suppl = args[:suppl] @annex = args[:annex] @year = args[:year] || year @month = roman_to_2digit args[:month] || month @amd = args[:amd] end def self.parse(id) id_parts = Parser.new.parse(id).to_h.transform_values(&:to_s) new(**id_parts) rescue Parslet::ParseFailed => e Util.error "`#{id}` is invalid ITU publication identifier\n" \ "#{e.parse_failure_cause.ascii_tree}" raise e end def to_h(with_type: true) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity hash = { prefix: prefix, sector: sector, code: code } hash[:type] = type if type && with_type hash[:suppl] = suppl if suppl hash[:annex] = annex if annex hash[:year] = year if year hash[:month] = month if month hash[:amd] = amd if amd hash end def to_ref to_s ref: true end def to_s(ref: false) # rubocop:disable Metrics/AbcSize s = "#{prefix}-#{sector}" s << " #{type}" if type && !ref s << " #{code}" s << " Suppl. #{suppl}" if suppl s << " Annex #{annex}" if annex s << date_to_s s << " Amd #{amd}" if amd s end def ===(other, ignore_args = []) hash = to_h with_type: false other_hash = other.to_h with_type: false hash.delete(:month) other_hash.delete(:month) hash.delete(:year) if ignore_args.include?(:year) other_hash.delete(:year) unless hash[:year] hash == other_hash end private def date_from_code(code) /(?.+?)-(?\d{6})(?:-I|$)/ =~ code return [code, nil, nil] unless cod && date [cod, date[0..3], date[4..5]] end def roman_to_2digit(num) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength return unless num roman_nums = { "I" => 1, "V" => 5, "X" => 10 } last = roman_nums[num[-1]] return num unless last return roman_nums[num].to_s.rjust(2, "0") if num.size == 1 num.chars.each_cons(2).reduce(last) do |acc, (a, b)| if roman_nums[a] < roman_nums[b] acc - roman_nums[a] else acc + roman_nums[a] end end.to_s.rjust(2, "0") end def date_to_s if month && year then " (#{month}/#{year})" elsif year then " (#{year})" else "" end end end end