# -*- coding: utf-8 -*- # # Copyright (C) 2012 Haruka Yoshihara # # License: Ruby's or LGPL # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . require "gettext/tools/po_entry" module GetText # PO stores PO entries like Hash. Each key of {POEntry} is msgctxt # and msgid. # PO[msgctxt, msgid] returns the {POEntry} containing msgctxt and # msgid. # If you specify msgid only, msgctxt is treated as nonexistent. # # @since 2.3.4 class PO include Enumerable class NonExistentEntryError < StandardError end # @!attribute [rw] order # The order is used to sort PO entries(objects of {POEntry}) in # {#to_s}. # @param [Symbol] order the name as order by sort. # Now :reference is allowed only. # @return [Symbol] the name as order by sort. attr_accessor :order def initialize(order=nil) @order = order || :references @entries = {} end # Returns {POEntry} containing msgctxt and msgid. # If you specify one argument, it is treated as msgid. # @overload [](msgid) # @!macro [new] po.[].argument # @param [String] msgid msgid contained returning {POEntry}. # @return [POEntry] # @!macro po.[].argument # @overload [](msgctxt, msgid) # @param [String] msgctxt msgctxt contained returning {POEntry}. # @!macro po.[].argument def [](msgctxt, msgid=nil) if msgid.nil? msgid = msgctxt msgctxt = nil end @entries[[msgctxt, msgid]] end # Stores {POEntry} or msgstr binding msgctxt and msgid. If you # specify msgstr, this method creates {POEntry} containing it. # If you specify the two argument, the first argument is treated # as msgid. # # @overload []=(msgid, po_entry) # @!macro [new] po.store.entry.arguments # @param [String] msgid msgid binded {POEntry}. # @param [POEntry] po_entry stored {POEntry}. # @!macro po.store.entry.arguments # @overload []=(msgctxt, msgid, po_entry) # @param [String] msgctxt msgctxt binded {POEntry}. # @!macro po.store.entry.arguments # @overload []=(msgid, msgstr) # @!macro [new] po.store.msgstr.arguments # @param [String] msgid msgid binded {POEntry}. # @param [String] msgstr msgstr contained {POEntry} stored PO. # This {POEntry} is generated in this method. # @!macro po.store.msgstr.arguments # @overload []=(msgctxt, msgid, msgstr) # @param [String] msgctxt msgctxt binded {POEntry}. # @!macro po.store.msgstr.arguments def []=(*arguments) case arguments.size when 2 msgctxt = nil msgid = arguments[0] value = arguments[1] when 3 msgctxt = arguments[0] msgid = arguments[1] value = arguments[2] else raise(ArgumentError, "[]=: wrong number of arguments(#{arguments.size} for 2..3)") end id = [msgctxt, msgid] if value.instance_of?(POEntry) @entries[id] = value return(value) end msgstr = value if @entries.has_key?(id) entry = @entries[id] else if msgctxt.nil? entry = POEntry.new(:normal) else entry = POEntry.new(:msgctxt) end @entries[id] = entry end entry.msgctxt = msgctxt entry.msgid = msgid entry.msgstr = msgstr entry end # Returns if PO stores {POEntry} containing msgctxt and msgid. # If you specify one argument, it is treated as msgid and msgctxt # is nil. # # @overload has_key?(msgid) # @!macro [new] po.has_key?.arguments # @param [String] msgid msgid contained {POEntry} checked if it be # stored PO. # @!macro po.has_key?.arguments # @overload has_key?(msgctxt, msgid) # @param [String] msgctxt msgctxt contained {POEntry} checked if # it be stored PO. # @!macro po.has_key?.arguments def has_key?(*arguments) case arguments.size when 1 msgctxt = nil msgid = arguments[0] when 2 msgctxt = arguments[0] msgid = arguments[1] else message = "has_key?: wrong number of arguments " + "(#{arguments.size} for 1..2)" raise(ArgumentError, message) end id = [msgctxt, msgid] @entries.has_key?(id) end # Calls block once for each {POEntry} as a block parameter. # @overload each(&block) # @yield [entry] # @yieldparam [POEntry] entry {POEntry} in PO. # @overload each # @return [Enumerator] Returns Enumerator for {POEntry}. def each if block_given? @entries.each do |_, entry| yield(entry) end else @entries.each_value end end # For {PoParer}. def set_comment(msgid, comment, msgctxt=nil) id = [msgctxt, msgid] self[*id] = nil unless @entries.has_key?(id) self[*id].comment = comment end # Formats each {POEntry} to the format of PO files and returns joined # them. # @see http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files # The description for Format of PO in GNU gettext manual # @return [String] Formatted and joined PO entries. It is used for # creating .po files. def to_s po_string = "" header_entry = @entries[[nil, ""]] po_string << header_entry.to_s unless header_entry.nil? content_entries = @entries.reject do |(_, msgid), _| msgid == :last or msgid.empty? end sort_by_order(content_entries).each do |msgid, entry| po_string << "\n" << entry.to_s end if @entries.has_key?([nil, :last]) po_string << "\n" << @entries[[nil, :last]].to_s end po_string end private def sort_by_order(entries) case @order when :references sorted_entries = sort_by_references(entries) when :msgid # TODO: sort by msgid alphabetically. else sorted_entries = entries.to_a end end def sort_by_references(entries) entries.each do |_, entry| entry.references = entry.references.sort do |reference, other| compare_references(reference, other) end end entries.sort do |msgid_entry, other_msgid_entry| # msgid_entry = [[msgctxt, msgid], POEntry] entry_first_reference = msgid_entry[1].references.first other_first_reference = other_msgid_entry[1].references.first compare_references(entry_first_reference, other_first_reference) end end def compare_references(reference, other) entry_source, entry_line_number = split_reference(reference) other_source, other_line_number = split_reference(other) if entry_source != other_source entry_source <=> other_source else entry_line_number <=> other_line_number end end def split_reference(reference) return ["", -1] if reference.nil? if /\A(.+):(\d+?)\z/ =~ reference [$1, $2.to_i] else [reference, -1] end end end end