zlocalize 4.1.0
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 +7 -0
- data/lib/zlocalize/backend.rb +246 -0
- data/lib/zlocalize/config.rb +62 -0
- data/lib/zlocalize/harvester.rb +123 -0
- data/lib/zlocalize/rails/active_record.rb +11 -0
- data/lib/zlocalize/rails/attached_translations.rb +122 -0
- data/lib/zlocalize/rails/decimal_attributes.rb +76 -0
- data/lib/zlocalize/rails/generators/initializer.rb +21 -0
- data/lib/zlocalize/rails/generators/templates/initializer_template.rb +53 -0
- data/lib/zlocalize/rails/generators/templates/translations_migration_template.rb +21 -0
- data/lib/zlocalize/rails/generators/translations_migration.rb +27 -0
- data/lib/zlocalize/rails/railtie.rb +28 -0
- data/lib/zlocalize/rails/tasks/harvest.rake +43 -0
- data/lib/zlocalize/rails/translated_columns.rb +73 -0
- data/lib/zlocalize/rails/translation.rb +10 -0
- data/lib/zlocalize/rails/translation_validator.rb +44 -0
- data/lib/zlocalize/source_parser.rb +756 -0
- data/lib/zlocalize/translation_file.rb +248 -0
- data/lib/zlocalize.rb +9 -0
- metadata +117 -0
@@ -0,0 +1,756 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'rdoc'
|
4
|
+
require 'rdoc/options'
|
5
|
+
require 'rdoc/ruby_lex'
|
6
|
+
require File.join(File.dirname(__FILE__),'translation_file')
|
7
|
+
require File.join(File.dirname(__FILE__),'harvester')
|
8
|
+
|
9
|
+
module ZLocalize
|
10
|
+
|
11
|
+
# base class for Expressions we're interested in
|
12
|
+
class Expression #:nodoc: all
|
13
|
+
attr_accessor :line_no
|
14
|
+
attr_accessor :char_no
|
15
|
+
attr_accessor :text
|
16
|
+
attr_accessor :sub_method
|
17
|
+
|
18
|
+
def initialize(line_no,char_no,text = '')
|
19
|
+
@line_no, @char_no, @text = line_no, char_no, text
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_text(t)
|
23
|
+
@text = t
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# puts spaces before the output generated by +display+. Used for debugging purposes.
|
28
|
+
def prefix(indent)
|
29
|
+
' ' * (indent*INDENT_STEP)
|
30
|
+
end
|
31
|
+
|
32
|
+
# display this Expression as a string. Used for debugging purposes
|
33
|
+
def display(indent = 0)
|
34
|
+
i = prefix(indent)
|
35
|
+
i << "#{self.class.name} (#{line_no},#{char_no}): #{@text}\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Since Expression is an abstract class, no output is generated
|
39
|
+
# (descendants redefine this method)
|
40
|
+
def to_translation_entry(filename)
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_entry_string
|
45
|
+
@text.force_encoding("UTF-8")
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_translate_call
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end # class Expression
|
52
|
+
|
53
|
+
# Basic identifier expression
|
54
|
+
class IdentifierExpression < Expression #:nodoc: all
|
55
|
+
attr_accessor :name
|
56
|
+
attr_accessor :parameters
|
57
|
+
|
58
|
+
def initialize(line_no,char_no,name)
|
59
|
+
@name = name
|
60
|
+
@parameters = []
|
61
|
+
@sub_method = nil
|
62
|
+
super(line_no,char_no)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Used for debugging
|
66
|
+
def display(indent = 0)
|
67
|
+
i = prefix(indent)
|
68
|
+
i << "#{self.class.name} (#{line_no},#{char_no}): #{@name} (#{parameters.size} parameters)\n"
|
69
|
+
@parameters.each { |p| i << p.display(indent+2) }
|
70
|
+
unless @sub_method.nil?
|
71
|
+
i << prefix(indent+1) + "sub-method:\n"
|
72
|
+
i << @sub_method.display(indent+2)
|
73
|
+
end
|
74
|
+
i
|
75
|
+
end
|
76
|
+
|
77
|
+
# Convert ourselves to a Hash or an Array of Hashes which each represent
|
78
|
+
# a call to a translate method.
|
79
|
+
# Will only output something if this expression is an actual call to <tt>_</tt> or <tt>n_</tt>
|
80
|
+
def to_translation_entry(filename)
|
81
|
+
if ['_','n_'].include?(@name)
|
82
|
+
params = @parameters.dup
|
83
|
+
elsif @name == 'ZLocalize' && @sub_method.is_a?(IdentifierExpression) && ['pluralize','translate'].include?(@sub_method.name)
|
84
|
+
params = @sub_method.parameters.dup
|
85
|
+
else
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
if params.size > 0
|
89
|
+
p1 = params.shift
|
90
|
+
# we only collect litteral strings and Arrays
|
91
|
+
entry = []
|
92
|
+
if [StringExpression,ArrayExpression].include?(p1.class)
|
93
|
+
entry << TranslationEntry.new( 'plural' => @name == 'n_',
|
94
|
+
'source' => p1.to_entry_string,
|
95
|
+
'references' => [ "#{filename}:#{p1.line_no}" ])
|
96
|
+
end
|
97
|
+
# collect all other parameters to this call, in case they are themselves calls to nested _("")
|
98
|
+
params.each { |p| entry << p.to_translation_entry(filename) }
|
99
|
+
return entry.flatten.compact
|
100
|
+
else
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_entry_string
|
106
|
+
"(identifier '#{name}')".force_encoding("UTF-8")
|
107
|
+
end
|
108
|
+
|
109
|
+
end # class IdentifierExpression
|
110
|
+
|
111
|
+
# a String Expression (litteral occurence of a string)
|
112
|
+
class StringExpression < IdentifierExpression #:nodoc: all
|
113
|
+
|
114
|
+
def display(indent = 0)
|
115
|
+
i = prefix(indent)
|
116
|
+
i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
|
117
|
+
unless @sub_method.nil?
|
118
|
+
i << prefix(indent+1) + "sub-method:\n"
|
119
|
+
i << @sub_method.display(indent+2)
|
120
|
+
end
|
121
|
+
i
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_entry_string
|
125
|
+
ZLocalize.clean_ruby_string(@name).force_encoding("UTF-8")
|
126
|
+
# @name
|
127
|
+
end
|
128
|
+
|
129
|
+
end #class StringExpression
|
130
|
+
|
131
|
+
# a Number Expression
|
132
|
+
class NumberExpression < IdentifierExpression #:nodoc: all
|
133
|
+
|
134
|
+
def display(indent = 0)
|
135
|
+
i = prefix(indent)
|
136
|
+
i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
|
137
|
+
unless @sub_method.nil?
|
138
|
+
i << prefix(indent+1) + "sub-method:\n"
|
139
|
+
i << @sub_method.display(indent+2)
|
140
|
+
end
|
141
|
+
i
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_entry_string
|
145
|
+
"\"(number '#{@name}')\"".force_encoding("UTF-8")
|
146
|
+
end
|
147
|
+
|
148
|
+
end # class NumberExpression
|
149
|
+
|
150
|
+
# a Symbol Expression
|
151
|
+
class SymbolExpression < IdentifierExpression #:nodoc: all
|
152
|
+
|
153
|
+
def display(indent = 0)
|
154
|
+
i = prefix(indent)
|
155
|
+
i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
|
156
|
+
unless @sub_method.nil?
|
157
|
+
i << prefix(indent+1) + "sub-method:\n"
|
158
|
+
i << @sub_method.display(indent+2)
|
159
|
+
end
|
160
|
+
i
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_entry_string
|
164
|
+
"\"(symbol #{@name})\"".force_encoding("UTF-8")
|
165
|
+
end
|
166
|
+
|
167
|
+
end # class SymbolExpression
|
168
|
+
|
169
|
+
class RangeExpression < IdentifierExpression #:nodoc: all
|
170
|
+
attr_accessor :low
|
171
|
+
attr_accessor :high
|
172
|
+
|
173
|
+
def display(indent = 0)
|
174
|
+
p = prefix(indent)
|
175
|
+
i = p + "#{self.class.name} (#{line_no},#{char_no})\n"
|
176
|
+
i << p + "low:\n" + low.display(indent+1)
|
177
|
+
i << p + "high:\n" + high.display(indent+1)
|
178
|
+
i << "\n"
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_entry_string
|
182
|
+
return "\"(range)\"".force_encoding("UTF-8")
|
183
|
+
end
|
184
|
+
|
185
|
+
end # class RangeExpression
|
186
|
+
|
187
|
+
# an Array declaration. It will hold its elements as a list of Expressions
|
188
|
+
class ArrayExpression < IdentifierExpression #:nodoc: all
|
189
|
+
|
190
|
+
attr_accessor :elements
|
191
|
+
|
192
|
+
def initialize(line_no,char_no)
|
193
|
+
super(line_no,char_no,'array')
|
194
|
+
@elements = []
|
195
|
+
end
|
196
|
+
|
197
|
+
def display(indent = 0)
|
198
|
+
i = super(indent)
|
199
|
+
@elements.each { |e| i << e.display(indent+1) }
|
200
|
+
i
|
201
|
+
end
|
202
|
+
|
203
|
+
def to_translation_entry(filename)
|
204
|
+
@elements.collect { |elem| elem.to_translation_entry(filename) }.flatten.compact
|
205
|
+
end
|
206
|
+
|
207
|
+
def to_entry_string
|
208
|
+
@elements.collect { |e| e.to_entry_string }
|
209
|
+
# '[' + @elements.collect { |e| e.to_entry_string }.join(', ') + ']'
|
210
|
+
end
|
211
|
+
|
212
|
+
end # class ArrayExpression
|
213
|
+
|
214
|
+
# a Hash declaration. Elements will be a Hash in which each element
|
215
|
+
# will itself be a HashElementExpression like this:
|
216
|
+
# { :key => Expression, :value => Expression }
|
217
|
+
class HashExpression < IdentifierExpression #:nodoc: all
|
218
|
+
|
219
|
+
attr_accessor :elements
|
220
|
+
|
221
|
+
def initialize(line_no,char_no)
|
222
|
+
super(line_no,char_no,'hash')
|
223
|
+
@elements = []
|
224
|
+
end
|
225
|
+
|
226
|
+
def display(indent = 0)
|
227
|
+
i = super(indent)
|
228
|
+
@elements.each { |e| i << e.display(indent) }
|
229
|
+
i
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_translation_entry(filename)
|
233
|
+
@elements.collect { |elem| elem.to_translation_entry(filename) }.flatten.compact
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_entry_string
|
237
|
+
'{ ' + @elements.collect { |e| e.to_entry_string }.compact.join(',') + ' }'.force_encoding("UTF-8")
|
238
|
+
end
|
239
|
+
|
240
|
+
end # class HashExpression
|
241
|
+
|
242
|
+
# a Hash Element. It holds the key and the value of the element as Expressions
|
243
|
+
class HashElementExpression < IdentifierExpression #:nodoc: all
|
244
|
+
attr_accessor :key
|
245
|
+
attr_accessor :value
|
246
|
+
|
247
|
+
def initialize(line_no,char_no)
|
248
|
+
super(line_no,char_no,'hashElement')
|
249
|
+
end
|
250
|
+
|
251
|
+
def display(indent = 0)
|
252
|
+
i = super(indent)
|
253
|
+
i << prefix(indent) + "key:\n" + @key.display(indent+1)
|
254
|
+
i << prefix(indent) + "value:\n" + @value.display(indent+1)
|
255
|
+
end
|
256
|
+
|
257
|
+
def to_translation_entry(filename)
|
258
|
+
r = [key.to_translation_entry(filename),value.to_translation_entry(filename)].flatten.compact
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_entry_string
|
262
|
+
"#{key.to_entry_string} => #{value.to_entry_string}".force_encoding("UTF-8")
|
263
|
+
end
|
264
|
+
|
265
|
+
end # class HashElementExpression
|
266
|
+
|
267
|
+
# an Operator expression holds the operator itself, and the operands
|
268
|
+
class OperatorExpression < Expression #:nodoc: all
|
269
|
+
|
270
|
+
attr_accessor :operator
|
271
|
+
attr_accessor :operands
|
272
|
+
|
273
|
+
def initialize(line_no,char_no,op)
|
274
|
+
super(line_no,char_no,op)
|
275
|
+
@operands = []
|
276
|
+
@operator = op
|
277
|
+
end
|
278
|
+
|
279
|
+
def display(indent = 0)
|
280
|
+
p = prefix(indent)
|
281
|
+
i = p + "#{self.class.name} (#{line_no},#{char_no})\n"
|
282
|
+
@operands.each { |o| i << o.display(indent+1) }
|
283
|
+
i
|
284
|
+
end
|
285
|
+
|
286
|
+
def to_translation_entry(filename)
|
287
|
+
@operands.collect { |o| o.to_translation_entry(filename) }.flatten.compact
|
288
|
+
end
|
289
|
+
|
290
|
+
def to_entry_string
|
291
|
+
@operands.collect { |o| o.to_entry_string }.join(" #{@operator} ").force_encoding("UTF-8")
|
292
|
+
end
|
293
|
+
|
294
|
+
end # class OperatorExpression
|
295
|
+
|
296
|
+
# represents an 'inline-if' expression, as in:
|
297
|
+
# something ? when_true : when_false
|
298
|
+
class ConditionalExpression < Expression #:nodoc: all
|
299
|
+
attr_accessor :condition
|
300
|
+
attr_accessor :true_expr
|
301
|
+
attr_accessor :false_expr
|
302
|
+
|
303
|
+
def initialize(line_to,char_no)
|
304
|
+
super(line_to,char_no,'?')
|
305
|
+
end
|
306
|
+
|
307
|
+
def display(indent = 0)
|
308
|
+
i = prefix(indent) + "#{self.class.name} (#{line_no},#{char_no})\n"
|
309
|
+
i << prefix(indent+1) + "condition:\n"
|
310
|
+
i << @condition.display(indent+2)
|
311
|
+
i << prefix(indent+1) + "when true:\n"
|
312
|
+
i << @true_expr.display(indent+2)
|
313
|
+
i << prefix(indent+1) + "when false:\n"
|
314
|
+
i << @false_expr.display(indent+2)
|
315
|
+
i
|
316
|
+
end
|
317
|
+
|
318
|
+
def to_translation_entry(filename)
|
319
|
+
[condition.to_translation_entry(filename),
|
320
|
+
true_expr.to_translation_entry(filename),
|
321
|
+
false_expr.to_translation_entry(filename)].flatten.compact
|
322
|
+
end
|
323
|
+
|
324
|
+
def to_entry_string
|
325
|
+
"#{conditions.as_entry_string} ? #{true_expr.as_entry_string} : #{false_expr.as_entry_string}".force_encoding("UTF-8")
|
326
|
+
end
|
327
|
+
|
328
|
+
end # class ConditionalExpression
|
329
|
+
|
330
|
+
# any other Expression that we don't really care about
|
331
|
+
class DontCareExpression < Expression #:nodoc: all
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
OPERATOR_TOKENS = [RDoc::RubyToken::TkGT, RDoc::RubyToken::TkLT, RDoc::RubyToken::TkPLUS,
|
336
|
+
RDoc::RubyToken::TkMINUS, RDoc::RubyToken::TkMULT, RDoc::RubyToken::TkSTAR,
|
337
|
+
RDoc::RubyToken::TkDIV,
|
338
|
+
RDoc::RubyToken::TkMOD, RDoc::RubyToken::TkBITOR, RDoc::RubyToken::TkBITXOR,
|
339
|
+
RDoc::RubyToken::TkBITAND, RDoc::RubyToken::TkBITNOT, RDoc::RubyToken::TkNOTOP,
|
340
|
+
RDoc::RubyToken::TkPOW, RDoc::RubyToken::TkCMP, RDoc::RubyToken::TkEQ,
|
341
|
+
RDoc::RubyToken::TkEQQ, RDoc::RubyToken::TkNEQ, RDoc::RubyToken::TkGEQ,
|
342
|
+
RDoc::RubyToken::TkLEQ, RDoc::RubyToken::TkANDOP, RDoc::RubyToken::TkOROP,
|
343
|
+
RDoc::RubyToken::TkMATCH, RDoc::RubyToken::TkNMATCH, RDoc::RubyToken::TkLSHFT,
|
344
|
+
RDoc::RubyToken::TkRSHFT]
|
345
|
+
|
346
|
+
UNARY_OP_TOKENS = [RDoc::RubyToken::TkPLUS,RDoc::RubyToken::TkMINUS,RDoc::RubyToken::TkBITNOT,RDoc::RubyToken::TkNOTOP]
|
347
|
+
|
348
|
+
|
349
|
+
# Parses a Ruby (or ERB) file and generates a list of language tokens
|
350
|
+
class SourceParser
|
351
|
+
|
352
|
+
attr_accessor :translate_calls
|
353
|
+
attr_accessor :in_hash
|
354
|
+
attr_accessor :filename
|
355
|
+
attr_accessor :root
|
356
|
+
|
357
|
+
def initialize(filename, root, is_erb = false)
|
358
|
+
@in_hash = 0
|
359
|
+
@translate_calls = []
|
360
|
+
@root = File.expand_path(root).downcase
|
361
|
+
@filename = File.expand_path(filename).downcase
|
362
|
+
@relative_filename = @filename.gsub(@root,'')
|
363
|
+
content = File.open(filename, "r") { |f| f.read }
|
364
|
+
if is_erb
|
365
|
+
content = ERB.new(content).src
|
366
|
+
end
|
367
|
+
|
368
|
+
@lex = RDoc::RubyLex.new(content,RDoc::Options.new)
|
369
|
+
@token_list = []
|
370
|
+
while tk = @lex.token
|
371
|
+
@token_list << tk
|
372
|
+
end
|
373
|
+
@token_index = -1
|
374
|
+
@last_token_index = @token_list.size
|
375
|
+
end
|
376
|
+
|
377
|
+
def get_tk
|
378
|
+
@token_index += 1
|
379
|
+
if @token_index < @last_token_index
|
380
|
+
@tk = @token_list[@token_index]
|
381
|
+
else
|
382
|
+
@tk = nil
|
383
|
+
end
|
384
|
+
@tk
|
385
|
+
end
|
386
|
+
|
387
|
+
def peek_tk
|
388
|
+
peek_index = @token_index + 1
|
389
|
+
return peek_index < @last_token_index ? @token_list[peek_index] : nil
|
390
|
+
end
|
391
|
+
|
392
|
+
def rewind
|
393
|
+
@token_index = -1
|
394
|
+
end
|
395
|
+
|
396
|
+
def skip_white_space
|
397
|
+
while [RDoc::RubyToken::TkNL, RDoc::RubyToken::TkSPACE].include?(@tk.class)
|
398
|
+
get_tk
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# simply list the tokens in the source file
|
403
|
+
def list_tokens
|
404
|
+
while get_tk
|
405
|
+
puts @tk.inspect
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def parse_error(tk,msg)
|
410
|
+
raise(ParseError,"\n\nIn file #{@filename}, on line #{tk.line_no} at position #{tk.char_no}: " + msg + "\n\n")
|
411
|
+
end
|
412
|
+
|
413
|
+
# we simply detect calls to the ZLocalize methods and parse their parameter list
|
414
|
+
def parse
|
415
|
+
rewind
|
416
|
+
while get_tk
|
417
|
+
if @tk.is_a?(RDoc::RubyToken::TkCONSTANT) && @tk.text = 'ZLocalize'
|
418
|
+
get_tk # should be a dot
|
419
|
+
get_tk # either 'translate' or 'pluralize'
|
420
|
+
parse_translate_call if ['translate','pluralize'].include?(@tk.text)
|
421
|
+
elsif @tk.is_a?(RDoc::RubyToken::TkIDENTIFIER)
|
422
|
+
parse_translate_call if ['_','n_'].include?(@tk.text)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
# parse a call to one of the translation methods <tt>_(...)</tt>, <tt>n_(...)</tt>
|
428
|
+
def parse_translate_call
|
429
|
+
m = IdentifierExpression.new(@tk.line_no,@tk.char_no,@tk.text)
|
430
|
+
get_tk
|
431
|
+
skip_white_space
|
432
|
+
expect_r_paren = @tk.is_a?(RDoc::RubyToken::TkLPAREN)
|
433
|
+
get_tk if expect_r_paren
|
434
|
+
m.parameters = parse_parameters
|
435
|
+
if expect_r_paren && !@tk.is_a?(RDoc::RubyToken::TkRPAREN)
|
436
|
+
parse_error(@tk,"')' expected but '#{@tk.text}' (#{@tk.class.name}) found")
|
437
|
+
end
|
438
|
+
@translate_calls << m
|
439
|
+
m
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
# parse the list of actual parameters to a method
|
444
|
+
def parse_parameters
|
445
|
+
parameters = []
|
446
|
+
skip_white_space
|
447
|
+
return if @tk.is_a?(RDoc::RubyToken::TkRPAREN)
|
448
|
+
|
449
|
+
parameters << parse_expression
|
450
|
+
skip_white_space
|
451
|
+
while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
|
452
|
+
get_tk
|
453
|
+
parameters << parse_expression
|
454
|
+
skip_white_space
|
455
|
+
end
|
456
|
+
parameters
|
457
|
+
end
|
458
|
+
|
459
|
+
# parse identifier(...), identifier[...] or identifier{...}
|
460
|
+
def parse_identifier
|
461
|
+
case @tk
|
462
|
+
when RDoc::RubyToken::TkLPAREN
|
463
|
+
ident_text = "(Method From Stack)"
|
464
|
+
when RDoc::RubyToken::TkfLBRACK
|
465
|
+
ident_text = "(Array Access)"
|
466
|
+
else
|
467
|
+
ident_text = @tk.text
|
468
|
+
line, char = @tk.line_no, @tk.char_no
|
469
|
+
get_tk
|
470
|
+
# check for a Ruby 2.0 Hash key symbol (identifier directly followed by a COLON)
|
471
|
+
if @tk.is_a?(RDoc::RubyToken::TkCOLON) || @tk.is_a?(RDoc::RubyToken::TkSYMBEG)
|
472
|
+
expr = SymbolExpression.new(line,char,'')
|
473
|
+
expr.set_text(ident_text + ':')
|
474
|
+
expr.name = ident_text + ':'
|
475
|
+
get_tk
|
476
|
+
return expr
|
477
|
+
end
|
478
|
+
end
|
479
|
+
ident = IdentifierExpression.new(@tk.line_no,@tk.char_no,ident_text)
|
480
|
+
while(@tk.is_a?(RDoc::RubyToken::TkCOLON2))
|
481
|
+
get_tk
|
482
|
+
ident.name << '::' + @tk.text
|
483
|
+
get_tk
|
484
|
+
end
|
485
|
+
|
486
|
+
case @tk
|
487
|
+
when RDoc::RubyToken::TkLPAREN
|
488
|
+
get_tk
|
489
|
+
ident.parameters = parse_parameters
|
490
|
+
unless @tk.is_a?(RDoc::RubyToken::TkRPAREN)
|
491
|
+
parse_error(@tk,"')' expected but '#{@tk.text}' found")
|
492
|
+
end
|
493
|
+
get_tk
|
494
|
+
when RDoc::RubyToken::TkfLBRACK
|
495
|
+
get_tk
|
496
|
+
ident.parameters = parse_parameters
|
497
|
+
unless @tk.is_a?(RDoc::RubyToken::TkRBRACK)
|
498
|
+
parse_error(@tk,"']' expected but '#{@tk.text}' found")
|
499
|
+
end
|
500
|
+
get_tk
|
501
|
+
end # case @tk
|
502
|
+
parse_identifier_method_call(ident)
|
503
|
+
end # parse_identifier
|
504
|
+
|
505
|
+
# parse the additional methods in an expression
|
506
|
+
# for example:
|
507
|
+
# self.method.sub_method(1).another_sub_method(2)
|
508
|
+
def parse_identifier_method_call(ident)
|
509
|
+
case @tk
|
510
|
+
when RDoc::RubyToken::TkDOT
|
511
|
+
get_tk
|
512
|
+
ident.sub_method = parse_identifier
|
513
|
+
when RDoc::RubyToken::TkLPAREN, RDoc::RubyToken::TkfLBRACK
|
514
|
+
ident.sub_method = parse_identifier
|
515
|
+
end
|
516
|
+
ident
|
517
|
+
end
|
518
|
+
|
519
|
+
# check if the expression currently being parsed as a range specifier
|
520
|
+
def check_for_range_expression(expr)
|
521
|
+
skip_white_space
|
522
|
+
if [RDoc::RubyToken::TkDOT2,RDoc::RubyToken::TkDOT3].include?(@tk.class)
|
523
|
+
range_expr = RangeExpression.new(expr.line_no,expr.char_no,'')
|
524
|
+
range_expr.low = expr
|
525
|
+
get_tk
|
526
|
+
range_expr.high = parse_expression
|
527
|
+
return range_expr
|
528
|
+
else
|
529
|
+
return expr
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
# check if the expression is a hash element declaration (i.e. is it followed by <tt>=></tt>)
|
534
|
+
def check_for_hash_element_expression(expr)
|
535
|
+
return expr if @in_hash > 0
|
536
|
+
if expr.is_a?(SymbolExpression) && expr.text =~ /:$/
|
537
|
+
he_expr = HashElementExpression.new(expr.line_no,expr.char_no)
|
538
|
+
he_expr.key = expr
|
539
|
+
skip_white_space
|
540
|
+
he_expr.value = parse_expression
|
541
|
+
# get_tk
|
542
|
+
return he_expr
|
543
|
+
else
|
544
|
+
skip_white_space
|
545
|
+
if @tk.is_a?(RDoc::RubyToken::TkASSIGN)
|
546
|
+
get_tk
|
547
|
+
if @tk.is_a?(RDoc::RubyToken::TkGT)
|
548
|
+
he_expr = HashElementExpression.new(expr.line_no,expr.char_no)
|
549
|
+
he_expr.key = expr
|
550
|
+
get_tk
|
551
|
+
he_expr.value = parse_expression
|
552
|
+
return he_expr
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
return expr
|
557
|
+
end
|
558
|
+
|
559
|
+
# parse an Expression, which can be either a simple identifier,
|
560
|
+
# a method call with parameters, an operator expression (as in 1 + 1)
|
561
|
+
# or an 'inline-if' expression
|
562
|
+
def parse_expression
|
563
|
+
skip_white_space
|
564
|
+
expr1 = parse_operand
|
565
|
+
skip_white_space
|
566
|
+
if OPERATOR_TOKENS.include?(@tk.class)
|
567
|
+
expr = OperatorExpression.new(expr1.line_no,expr1.char_no,'')
|
568
|
+
expr.operands << expr1
|
569
|
+
while OPERATOR_TOKENS.include?(@tk.class)
|
570
|
+
get_tk
|
571
|
+
expr.operands << parse_operand
|
572
|
+
end
|
573
|
+
else
|
574
|
+
expr = expr1
|
575
|
+
end
|
576
|
+
skip_white_space
|
577
|
+
if @tk.is_a?(RDoc::RubyToken::TkQUESTION)
|
578
|
+
get_tk
|
579
|
+
expr2 = ConditionalExpression.new(expr.line_no,expr.char_no)
|
580
|
+
expr2.condition = expr1
|
581
|
+
expr2.true_expr = parse_expression
|
582
|
+
skip_white_space
|
583
|
+
unless @tk.is_a?(RDoc::RubyToken::TkCOLON)
|
584
|
+
parse_error(@tk,"':' expected but #{@tk.text} found")
|
585
|
+
end
|
586
|
+
get_tk
|
587
|
+
expr2.false_expr = parse_expression
|
588
|
+
expr = expr2
|
589
|
+
end
|
590
|
+
expr
|
591
|
+
end
|
592
|
+
|
593
|
+
# parse and return an operand to an OperatorExpression
|
594
|
+
def parse_operand
|
595
|
+
skip_white_space
|
596
|
+
while UNARY_OP_TOKENS.include?(@tk.class)
|
597
|
+
get_tk
|
598
|
+
skip_white_space
|
599
|
+
end
|
600
|
+
case @tk
|
601
|
+
when RDoc::RubyToken::TkLPAREN, RDoc::RubyToken::TkfLPAREN
|
602
|
+
get_tk
|
603
|
+
expr = parse_expression
|
604
|
+
unless @tk.is_a?(RDoc::RubyToken::TkRPAREN)
|
605
|
+
parse_error(@tk,"')' expected but #{@tk.text} found (unmatched parenthesis)")
|
606
|
+
end
|
607
|
+
get_tk
|
608
|
+
expr = parse_identifier_method_call(expr) # ident.something
|
609
|
+
expr = check_for_range_expression(expr)
|
610
|
+
expr = check_for_hash_element_expression(expr)
|
611
|
+
when RDoc::RubyToken::TkSTRING, RDoc::RubyToken::TkDSTRING, RDoc::RubyToken::TkXSTRING, RDoc::RubyToken::TkREGEXP
|
612
|
+
expr = StringExpression.new(@tk.line_no,@tk.char_no, @tk.text)
|
613
|
+
get_tk
|
614
|
+
# check for Ruby 2.2 Hash symbol (string directly followed by a COLON)
|
615
|
+
if @tk.is_a?(RDoc::RubyToken::TkCOLON)
|
616
|
+
expr = SymbolExpression.new(expr.line_no,expr.char_no,expr.name + ':')
|
617
|
+
expr.text = expr.name
|
618
|
+
get_tk
|
619
|
+
end
|
620
|
+
expr = parse_identifier_method_call(expr)
|
621
|
+
expr = check_for_range_expression(expr)
|
622
|
+
expr = check_for_hash_element_expression(expr)
|
623
|
+
when RDoc::RubyToken::TkINTEGER, RDoc::RubyToken::TkFLOAT, RDoc::RubyToken::TkTRUE, RDoc::RubyToken::TkFALSE
|
624
|
+
expr = NumberExpression.new(@tk.line_no,@tk.char_no,@tk.text)
|
625
|
+
get_tk
|
626
|
+
expr = parse_identifier_method_call(expr)
|
627
|
+
expr = check_for_range_expression(expr)
|
628
|
+
expr = check_for_hash_element_expression(expr)
|
629
|
+
when RDoc::RubyToken::TkSYMBEG
|
630
|
+
expr = SymbolExpression.new(@tk.line_no,@tk.char_no,'')
|
631
|
+
get_tk
|
632
|
+
if @tk.text =~ /^[a-z]{1,}[a-z0-9\_]*?$/ #is_a?(RDoc::RubyToken::TkIDENTIFIER)
|
633
|
+
expr.set_text(':' + @tk.text)
|
634
|
+
expr.name = ':' + @tk.text
|
635
|
+
get_tk
|
636
|
+
expr = parse_identifier_method_call(expr)
|
637
|
+
expr = check_for_hash_element_expression(expr)
|
638
|
+
else
|
639
|
+
parse_error(@tk,"':' not followed by identifier or operator")
|
640
|
+
end
|
641
|
+
when RDoc::RubyToken::TkGVAR, RDoc::RubyToken::TkIVAR, RDoc::RubyToken::TkSELF, RDoc::RubyToken::TkNIL
|
642
|
+
expr = parse_identifier
|
643
|
+
when RDoc::RubyToken::TkCONSTANT, RDoc::RubyToken::TkIDENTIFIER
|
644
|
+
expr = parse_identifier
|
645
|
+
expr = check_for_hash_element_expression(expr)
|
646
|
+
when RDoc::RubyToken::TkLBRACK
|
647
|
+
expr = parse_array
|
648
|
+
expr = parse_identifier_method_call(expr)
|
649
|
+
when RDoc::RubyToken::TkLBRACE
|
650
|
+
expr = parse_hash
|
651
|
+
expr = parse_identifier_method_call(expr)
|
652
|
+
when RDoc::RubyToken::TkKW
|
653
|
+
# check for a Ruby 2.0 syntax hash key symbol (such as end: begin: class: case: etc...)
|
654
|
+
if peek_tk.is_a?(RDoc::RubyToken::TkCOLON)
|
655
|
+
ident_text, line, char = @tk.text, @tk.line_no, @tk.char_no
|
656
|
+
get_tk
|
657
|
+
expr = SymbolExpression.new(line,char,'')
|
658
|
+
expr.set_text(ident_text + ':')
|
659
|
+
expr.name = ident_text + ':'
|
660
|
+
get_tk
|
661
|
+
expr = check_for_hash_element_expression(expr)
|
662
|
+
end
|
663
|
+
end # case @tk
|
664
|
+
expr
|
665
|
+
end
|
666
|
+
|
667
|
+
# parse an Array declaration <tt>[...]</tt>
|
668
|
+
def parse_array
|
669
|
+
get_tk
|
670
|
+
array_expr = ArrayExpression.new(@tk.line_no,@tk.char_no)
|
671
|
+
skip_white_space
|
672
|
+
array_expr.elements << parse_expression
|
673
|
+
skip_white_space
|
674
|
+
while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
|
675
|
+
get_tk
|
676
|
+
array_expr.elements << parse_expression
|
677
|
+
skip_white_space
|
678
|
+
end
|
679
|
+
unless @tk.is_a?(RDoc::RubyToken::TkRBRACK)
|
680
|
+
parse_error(@tk,"']' expected but '#{@tk.text}' found")
|
681
|
+
end
|
682
|
+
get_tk
|
683
|
+
array_expr
|
684
|
+
end
|
685
|
+
|
686
|
+
# Parse a +Hash+ declaration <tt>{ ... }</tt>
|
687
|
+
def parse_hash
|
688
|
+
@in_hash += 1
|
689
|
+
get_tk
|
690
|
+
hash_expr = HashExpression.new(@tk.line_no,@tk.char_no)
|
691
|
+
skip_white_space
|
692
|
+
hash_expr.elements << parse_hash_element
|
693
|
+
skip_white_space
|
694
|
+
while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
|
695
|
+
get_tk
|
696
|
+
hash_expr.elements << parse_hash_element
|
697
|
+
skip_white_space
|
698
|
+
end
|
699
|
+
unless @tk.is_a?(RDoc::RubyToken::TkRBRACE)
|
700
|
+
parse_error(@tk,"'}' expected but '#{@tk.text}' found")
|
701
|
+
end
|
702
|
+
get_tk
|
703
|
+
@in_hash -= 1
|
704
|
+
hash_expr
|
705
|
+
end
|
706
|
+
|
707
|
+
# parse a Hash element within a +Hash+ declaration
|
708
|
+
def parse_hash_element
|
709
|
+
el = HashElementExpression.new(@tk.line_no,@tk.char_no)
|
710
|
+
el.key = parse_expression
|
711
|
+
if el.key.is_a?(SymbolExpression) && el.key.text =~ /:$/
|
712
|
+
skip_white_space
|
713
|
+
el.value = parse_expression
|
714
|
+
# get_tk
|
715
|
+
return el
|
716
|
+
else
|
717
|
+
skip_white_space
|
718
|
+
if @tk.is_a?(RDoc::RubyToken::TkASSIGN)
|
719
|
+
get_tk
|
720
|
+
if @tk.is_a?(RDoc::RubyToken::TkGT)
|
721
|
+
get_tk
|
722
|
+
skip_white_space
|
723
|
+
el.value = parse_expression
|
724
|
+
skip_white_space
|
725
|
+
return el
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
parse_error(@tk,"'=>' expected but '#{@tk.text}' found")
|
730
|
+
end
|
731
|
+
|
732
|
+
# return a Hash of all translation entries we collected
|
733
|
+
def translation_entries
|
734
|
+
entries = {}
|
735
|
+
self.translate_calls.each do |c|
|
736
|
+
e = c.to_translation_entry(@relative_filename)
|
737
|
+
if e.is_a?(Array)
|
738
|
+
e.each do |te|
|
739
|
+
if entries[te.source]
|
740
|
+
entries[te.source].references += te.references
|
741
|
+
else
|
742
|
+
entries[te.source] = te
|
743
|
+
end
|
744
|
+
end
|
745
|
+
elsif e.is_a?(TranslationEntry)
|
746
|
+
if entries[te.source]
|
747
|
+
entries[e.source].references += te.references
|
748
|
+
else
|
749
|
+
entries[e.source] = e
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
entries
|
754
|
+
end
|
755
|
+
end # class SourceParser
|
756
|
+
end # module ZLocalize
|