File: po.rb

package info (click to toggle)
ruby-gettext 3.3.3-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 6,424 kB
  • sloc: ruby: 9,643; makefile: 8
file content (275 lines) | stat: -rw-r--r-- 8,485 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2017  Kouhei Sutou <kou@clear-code.com>
# Copyright (C) 2012  Haruka Yoshihara <yoshihara@clear-code.com>
#
# 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 <http://www.gnu.org/licenses/>.

require "gettext/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 [:reference, :msgid] order (:reference) The sort key.
    #
    #     Use `:reference` for sorting by location that message is placed.
    #
    #     Use `:msgid` for sorting by msgid alphabetical order.
    #
    #     `:references` is deprecated since 3.0.4. It will be removed
    #     at 4.0.0. Use `:reference` instead.
    #
    #   @return [Symbol] the name as order by sort.
    attr_accessor :order

    def initialize(order=nil)
      @order = order || :reference
      @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(&block)
      @entries.each_value(&block)
    end

    # @return [Bool] `true` if there is no entry, `false` otherwise.
    def empty?
      @entries.empty?
    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
    # @param (see POEntry#to_s)
    # @return [String] Formatted and joined PO entries. It is used for
    #   creating .po files.
    def to_s(options={})
      po_string = String.new

      header_entry = @entries[[nil, ""]]
      unless header_entry.nil?
        po_string << header_entry.to_s(options.merge(:max_line_width => nil))
      end

      content_entries = @entries.reject do |(_, msgid), _|
        msgid == :last or msgid.empty?
      end

      sort(content_entries).each do |msgid, entry|
        po_string << "\n" unless po_string.empty?
        po_string << entry.to_s(options)
      end

      if @entries.has_key?([nil, :last])
        po_string << "\n" unless po_string.empty?
        po_string << @entries[[nil, :last]].to_s(options)
      end

      po_string
    end

    private
    def sort(entries)
      case @order
      when :reference, :references # :references is deprecated.
        sort_by_reference(entries)
      when :msgid
        sort_by_msgid(entries)
      else
        entries.to_a
      end
    end

    def sort_by_reference(entries)
      entries.each do |_, entry|
        entry.references = entry.references.sort do |reference, other|
          compare_reference(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_reference(entry_first_reference, other_first_reference)
      end
    end

    def compare_reference(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

    def sort_by_msgid(entries)
      entries.sort_by do |msgid_entry|
        # msgid_entry = [[msgctxt, msgid], POEntry]
        msgid_entry[0][1]
      end
    end
  end
end