zlocalize 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|