Codebase list mikutter / f2ecf09
Imported Upstream version 3.2.4+dfsg HIGUCHI Daisuke (VDR dai) 8 years ago
194 changed file(s) with 0 addition(s) and 27788 deletion(s). Raw diff Collapse all Expand all
core/skin/data/icon.png less more
Binary diff not shown
+0
-55
vendor/addressable/idna/native.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2015 Bob Aman
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #++
16
17
18 require "idn"
19
20 module Addressable
21 module IDNA
22 def self.punycode_encode(value)
23 IDN::Punycode.encode(value.to_s)
24 end
25
26 def self.punycode_decode(value)
27 IDN::Punycode.decode(value.to_s)
28 end
29
30 def self.unicode_normalize_kc(value)
31 IDN::Stringprep.nfkc_normalize(value.to_s)
32 end
33
34 def self.to_ascii(value)
35 value.to_s.split('.', -1).map do |segment|
36 if segment.size > 0
37 IDN::Idna.toASCII(segment)
38 else
39 ''
40 end
41 end.join('.')
42 end
43
44 def self.to_unicode(value)
45 value.to_s.split('.', -1).map do |segment|
46 if segment.size > 0
47 IDN::Idna.toUnicode(segment)
48 else
49 ''
50 end
51 end.join('.')
52 end
53 end
54 end
+0
-673
vendor/addressable/idna/pure.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2015 Bob Aman
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #++
16
17
18 module Addressable
19 module IDNA
20 # This module is loosely based on idn_actionmailer by Mick Staugaard,
21 # the unicode library by Yoshida Masato, and the punycode implementation
22 # by Kazuhiro Nishiyama. Most of the code was copied verbatim, but
23 # some reformatting was done, and some translation from C was done.
24 #
25 # Without their code to work from as a base, we'd all still be relying
26 # on the presence of libidn. Which nobody ever seems to have installed.
27 #
28 # Original sources:
29 # http://github.com/staugaard/idn_actionmailer
30 # http://www.yoshidam.net/Ruby.html#unicode
31 # http://rubyforge.org/frs/?group_id=2550
32
33
34 UNICODE_TABLE = File.expand_path(
35 File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data')
36 )
37
38 ACE_PREFIX = "xn--"
39
40 UTF8_REGEX = /\A(?:
41 [\x09\x0A\x0D\x20-\x7E] # ASCII
42 | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
43 | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
44 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
45 | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
46 | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
47 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
48 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
49 )*\z/mnx
50
51 UTF8_REGEX_MULTIBYTE = /(?:
52 [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
53 | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
54 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
55 | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
56 | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
57 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
58 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
59 )/mnx
60
61 # :startdoc:
62
63 # Converts from a Unicode internationalized domain name to an ASCII
64 # domain name as described in RFC 3490.
65 def self.to_ascii(input)
66 input = input.to_s unless input.is_a?(String)
67 input = input.dup
68 if input.respond_to?(:force_encoding)
69 input.force_encoding(Encoding::ASCII_8BIT)
70 end
71 if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE
72 parts = unicode_downcase(input).split('.')
73 parts.map! do |part|
74 if part.respond_to?(:force_encoding)
75 part.force_encoding(Encoding::ASCII_8BIT)
76 end
77 if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
78 ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
79 else
80 part
81 end
82 end
83 parts.join('.')
84 else
85 input
86 end
87 end
88
89 # Converts from an ASCII domain name to a Unicode internationalized
90 # domain name as described in RFC 3490.
91 def self.to_unicode(input)
92 input = input.to_s unless input.is_a?(String)
93 parts = input.split('.')
94 parts.map! do |part|
95 if part =~ /^#{ACE_PREFIX}/
96 punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
97 else
98 part
99 end
100 end
101 output = parts.join('.')
102 if output.respond_to?(:force_encoding)
103 output.force_encoding(Encoding::UTF_8)
104 end
105 output
106 end
107
108 # Unicode normalization form KC.
109 def self.unicode_normalize_kc(input)
110 input = input.to_s unless input.is_a?(String)
111 unpacked = input.unpack("U*")
112 unpacked =
113 unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
114 return unpacked.pack("U*")
115 end
116
117 ##
118 # Unicode aware downcase method.
119 #
120 # @api private
121 # @param [String] input
122 # The input string.
123 # @return [String] The downcased result.
124 def self.unicode_downcase(input)
125 input = input.to_s unless input.is_a?(String)
126 unpacked = input.unpack("U*")
127 unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
128 return unpacked.pack("U*")
129 end
130 (class <<self; private :unicode_downcase; end)
131
132 def self.unicode_compose(unpacked)
133 unpacked_result = []
134 length = unpacked.length
135
136 return unpacked if length == 0
137
138 starter = unpacked[0]
139 starter_cc = lookup_unicode_combining_class(starter)
140 starter_cc = 256 if starter_cc != 0
141 for i in 1...length
142 ch = unpacked[i]
143 cc = lookup_unicode_combining_class(ch)
144
145 if (starter_cc == 0 &&
146 (composite = unicode_compose_pair(starter, ch)) != nil)
147 starter = composite
148 startercc = lookup_unicode_combining_class(composite)
149 else
150 unpacked_result << starter
151 starter = ch
152 startercc = cc
153 end
154 end
155 unpacked_result << starter
156 return unpacked_result
157 end
158 (class <<self; private :unicode_compose; end)
159
160 def self.unicode_compose_pair(ch_one, ch_two)
161 if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
162 ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
163 # Hangul L + V
164 return HANGUL_SBASE + (
165 (ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
166 ) * HANGUL_TCOUNT
167 elsif ch_one >= HANGUL_SBASE &&
168 ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
169 (ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
170 ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
171 # Hangul LV + T
172 return ch_one + (ch_two - HANGUL_TBASE)
173 end
174
175 p = []
176 ucs4_to_utf8 = lambda do |ch|
177 # For some reason, rcov likes to drop BUS errors here.
178 if ch < 128
179 p << ch
180 elsif ch < 2048
181 p << (ch >> 6 | 192)
182 p << (ch & 63 | 128)
183 elsif ch < 0x10000
184 p << (ch >> 12 | 224)
185 p << (ch >> 6 & 63 | 128)
186 p << (ch & 63 | 128)
187 elsif ch < 0x200000
188 p << (ch >> 18 | 240)
189 p << (ch >> 12 & 63 | 128)
190 p << (ch >> 6 & 63 | 128)
191 p << (ch & 63 | 128)
192 elsif ch < 0x4000000
193 p << (ch >> 24 | 248)
194 p << (ch >> 18 & 63 | 128)
195 p << (ch >> 12 & 63 | 128)
196 p << (ch >> 6 & 63 | 128)
197 p << (ch & 63 | 128)
198 elsif ch < 0x80000000
199 p << (ch >> 30 | 252)
200 p << (ch >> 24 & 63 | 128)
201 p << (ch >> 18 & 63 | 128)
202 p << (ch >> 12 & 63 | 128)
203 p << (ch >> 6 & 63 | 128)
204 p << (ch & 63 | 128)
205 end
206 end
207
208 ucs4_to_utf8.call(ch_one)
209 ucs4_to_utf8.call(ch_two)
210
211 return lookup_unicode_composition(p)
212 end
213 (class <<self; private :unicode_compose_pair; end)
214
215 def self.unicode_sort_canonical(unpacked)
216 unpacked = unpacked.dup
217 i = 1
218 length = unpacked.length
219
220 return unpacked if length < 2
221
222 while i < length
223 last = unpacked[i-1]
224 ch = unpacked[i]
225 last_cc = lookup_unicode_combining_class(last)
226 cc = lookup_unicode_combining_class(ch)
227 if cc != 0 && last_cc != 0 && last_cc > cc
228 unpacked[i] = last
229 unpacked[i-1] = ch
230 i -= 1 if i > 1
231 else
232 i += 1
233 end
234 end
235 return unpacked
236 end
237 (class <<self; private :unicode_sort_canonical; end)
238
239 def self.unicode_decompose(unpacked)
240 unpacked_result = []
241 for cp in unpacked
242 if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
243 l, v, t = unicode_decompose_hangul(cp)
244 unpacked_result << l
245 unpacked_result << v if v
246 unpacked_result << t if t
247 else
248 dc = lookup_unicode_compatibility(cp)
249 unless dc
250 unpacked_result << cp
251 else
252 unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
253 end
254 end
255 end
256 return unpacked_result
257 end
258 (class <<self; private :unicode_decompose; end)
259
260 def self.unicode_decompose_hangul(codepoint)
261 sindex = codepoint - HANGUL_SBASE;
262 if sindex < 0 || sindex >= HANGUL_SCOUNT
263 l = codepoint
264 v = t = nil
265 return l, v, t
266 end
267 l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
268 v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
269 t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
270 if t == HANGUL_TBASE
271 t = nil
272 end
273 return l, v, t
274 end
275 (class <<self; private :unicode_decompose_hangul; end)
276
277 def self.lookup_unicode_combining_class(codepoint)
278 codepoint_data = UNICODE_DATA[codepoint]
279 (codepoint_data ?
280 (codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
281 0)
282 end
283 (class <<self; private :lookup_unicode_combining_class; end)
284
285 def self.lookup_unicode_compatibility(codepoint)
286 codepoint_data = UNICODE_DATA[codepoint]
287 (codepoint_data ?
288 codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
289 end
290 (class <<self; private :lookup_unicode_compatibility; end)
291
292 def self.lookup_unicode_lowercase(codepoint)
293 codepoint_data = UNICODE_DATA[codepoint]
294 (codepoint_data ?
295 (codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) :
296 codepoint)
297 end
298 (class <<self; private :lookup_unicode_lowercase; end)
299
300 def self.lookup_unicode_composition(unpacked)
301 return COMPOSITION_TABLE[unpacked]
302 end
303 (class <<self; private :lookup_unicode_composition; end)
304
305 HANGUL_SBASE = 0xac00
306 HANGUL_LBASE = 0x1100
307 HANGUL_LCOUNT = 19
308 HANGUL_VBASE = 0x1161
309 HANGUL_VCOUNT = 21
310 HANGUL_TBASE = 0x11a7
311 HANGUL_TCOUNT = 28
312 HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
313 HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
314
315 UNICODE_DATA_COMBINING_CLASS = 0
316 UNICODE_DATA_EXCLUSION = 1
317 UNICODE_DATA_CANONICAL = 2
318 UNICODE_DATA_COMPATIBILITY = 3
319 UNICODE_DATA_UPPERCASE = 4
320 UNICODE_DATA_LOWERCASE = 5
321 UNICODE_DATA_TITLECASE = 6
322
323 begin
324 if defined?(FakeFS)
325 fakefs_state = FakeFS.activated?
326 FakeFS.deactivate!
327 end
328 # This is a sparse Unicode table. Codepoints without entries are
329 # assumed to have the value: [0, 0, nil, nil, nil, nil, nil]
330 UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file|
331 Marshal.load(file.read)
332 end
333 ensure
334 if defined?(FakeFS)
335 FakeFS.activate! if fakefs_state
336 end
337 end
338
339 COMPOSITION_TABLE = {}
340 for codepoint, data in UNICODE_DATA
341 canonical = data[UNICODE_DATA_CANONICAL]
342 exclusion = data[UNICODE_DATA_EXCLUSION]
343
344 if canonical && exclusion == 0
345 COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint
346 end
347 end
348
349 UNICODE_MAX_LENGTH = 256
350 ACE_MAX_LENGTH = 256
351
352 PUNYCODE_BASE = 36
353 PUNYCODE_TMIN = 1
354 PUNYCODE_TMAX = 26
355 PUNYCODE_SKEW = 38
356 PUNYCODE_DAMP = 700
357 PUNYCODE_INITIAL_BIAS = 72
358 PUNYCODE_INITIAL_N = 0x80
359 PUNYCODE_DELIMITER = 0x2D
360
361 PUNYCODE_MAXINT = 1 << 64
362
363 PUNYCODE_PRINT_ASCII =
364 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
365 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
366 " !\"\#$%&'()*+,-./" +
367 "0123456789:;<=>?" +
368 "@ABCDEFGHIJKLMNO" +
369 "PQRSTUVWXYZ[\\]^_" +
370 "`abcdefghijklmno" +
371 "pqrstuvwxyz{|}~\n"
372
373 # Input is invalid.
374 class PunycodeBadInput < StandardError; end
375 # Output would exceed the space provided.
376 class PunycodeBigOutput < StandardError; end
377 # Input needs wider integers to process.
378 class PunycodeOverflow < StandardError; end
379
380 def self.punycode_encode(unicode)
381 unicode = unicode.to_s unless unicode.is_a?(String)
382 input = unicode.unpack("U*")
383 output = [0] * (ACE_MAX_LENGTH + 1)
384 input_length = input.size
385 output_length = [ACE_MAX_LENGTH]
386
387 # Initialize the state
388 n = PUNYCODE_INITIAL_N
389 delta = out = 0
390 max_out = output_length[0]
391 bias = PUNYCODE_INITIAL_BIAS
392
393 # Handle the basic code points:
394 input_length.times do |j|
395 if punycode_basic?(input[j])
396 if max_out - out < 2
397 raise PunycodeBigOutput,
398 "Output would exceed the space provided."
399 end
400 output[out] = input[j]
401 out += 1
402 end
403 end
404
405 h = b = out
406
407 # h is the number of code points that have been handled, b is the
408 # number of basic code points, and out is the number of characters
409 # that have been output.
410
411 if b > 0
412 output[out] = PUNYCODE_DELIMITER
413 out += 1
414 end
415
416 # Main encoding loop:
417
418 while h < input_length
419 # All non-basic code points < n have been
420 # handled already. Find the next larger one:
421
422 m = PUNYCODE_MAXINT
423 input_length.times do |j|
424 m = input[j] if (n...m) === input[j]
425 end
426
427 # Increase delta enough to advance the decoder's
428 # <n,i> state to <m,0>, but guard against overflow:
429
430 if m - n > (PUNYCODE_MAXINT - delta) / (h + 1)
431 raise PunycodeOverflow, "Input needs wider integers to process."
432 end
433 delta += (m - n) * (h + 1)
434 n = m
435
436 input_length.times do |j|
437 # Punycode does not need to check whether input[j] is basic:
438 if input[j] < n
439 delta += 1
440 if delta == 0
441 raise PunycodeOverflow,
442 "Input needs wider integers to process."
443 end
444 end
445
446 if input[j] == n
447 # Represent delta as a generalized variable-length integer:
448
449 q = delta; k = PUNYCODE_BASE
450 while true
451 if out >= max_out
452 raise PunycodeBigOutput,
453 "Output would exceed the space provided."
454 end
455 t = (
456 if k <= bias
457 PUNYCODE_TMIN
458 elsif k >= bias + PUNYCODE_TMAX
459 PUNYCODE_TMAX
460 else
461 k - bias
462 end
463 )
464 break if q < t
465 output[out] =
466 punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
467 out += 1
468 q = (q - t) / (PUNYCODE_BASE - t)
469 k += PUNYCODE_BASE
470 end
471
472 output[out] = punycode_encode_digit(q)
473 out += 1
474 bias = punycode_adapt(delta, h + 1, h == b)
475 delta = 0
476 h += 1
477 end
478 end
479
480 delta += 1
481 n += 1
482 end
483
484 output_length[0] = out
485
486 outlen = out
487 outlen.times do |j|
488 c = output[j]
489 unless c >= 0 && c <= 127
490 raise Exception, "Invalid output char."
491 end
492 unless PUNYCODE_PRINT_ASCII[c]
493 raise PunycodeBadInput, "Input is invalid."
494 end
495 end
496
497 output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
498 end
499 (class <<self; private :punycode_encode; end)
500
501 def self.punycode_decode(punycode)
502 input = []
503 output = []
504
505 if ACE_MAX_LENGTH * 2 < punycode.size
506 raise PunycodeBigOutput, "Output would exceed the space provided."
507 end
508 punycode.each_byte do |c|
509 unless c >= 0 && c <= 127
510 raise PunycodeBadInput, "Input is invalid."
511 end
512 input.push(c)
513 end
514
515 input_length = input.length
516 output_length = [UNICODE_MAX_LENGTH]
517
518 # Initialize the state
519 n = PUNYCODE_INITIAL_N
520
521 out = i = 0
522 max_out = output_length[0]
523 bias = PUNYCODE_INITIAL_BIAS
524
525 # Handle the basic code points: Let b be the number of input code
526 # points before the last delimiter, or 0 if there is none, then
527 # copy the first b code points to the output.
528
529 b = 0
530 input_length.times do |j|
531 b = j if punycode_delimiter?(input[j])
532 end
533 if b > max_out
534 raise PunycodeBigOutput, "Output would exceed the space provided."
535 end
536
537 b.times do |j|
538 unless punycode_basic?(input[j])
539 raise PunycodeBadInput, "Input is invalid."
540 end
541 output[out] = input[j]
542 out+=1
543 end
544
545 # Main decoding loop: Start just after the last delimiter if any
546 # basic code points were copied; start at the beginning otherwise.
547
548 in_ = b > 0 ? b + 1 : 0
549 while in_ < input_length
550
551 # in_ is the index of the next character to be consumed, and
552 # out is the number of code points in the output array.
553
554 # Decode a generalized variable-length integer into delta,
555 # which gets added to i. The overflow checking is easier
556 # if we increase i as we go, then subtract off its starting
557 # value at the end to obtain delta.
558
559 oldi = i; w = 1; k = PUNYCODE_BASE
560 while true
561 if in_ >= input_length
562 raise PunycodeBadInput, "Input is invalid."
563 end
564 digit = punycode_decode_digit(input[in_])
565 in_+=1
566 if digit >= PUNYCODE_BASE
567 raise PunycodeBadInput, "Input is invalid."
568 end
569 if digit > (PUNYCODE_MAXINT - i) / w
570 raise PunycodeOverflow, "Input needs wider integers to process."
571 end
572 i += digit * w
573 t = (
574 if k <= bias
575 PUNYCODE_TMIN
576 elsif k >= bias + PUNYCODE_TMAX
577 PUNYCODE_TMAX
578 else
579 k - bias
580 end
581 )
582 break if digit < t
583 if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t)
584 raise PunycodeOverflow, "Input needs wider integers to process."
585 end
586 w *= PUNYCODE_BASE - t
587 k += PUNYCODE_BASE
588 end
589
590 bias = punycode_adapt(i - oldi, out + 1, oldi == 0)
591
592 # I was supposed to wrap around from out + 1 to 0,
593 # incrementing n each time, so we'll fix that now:
594
595 if i / (out + 1) > PUNYCODE_MAXINT - n
596 raise PunycodeOverflow, "Input needs wider integers to process."
597 end
598 n += i / (out + 1)
599 i %= out + 1
600
601 # Insert n at position i of the output:
602
603 # not needed for Punycode:
604 # raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base
605 if out >= max_out
606 raise PunycodeBigOutput, "Output would exceed the space provided."
607 end
608
609 #memmove(output + i + 1, output + i, (out - i) * sizeof *output)
610 output[i + 1, out - i] = output[i, out - i]
611 output[i] = n
612 i += 1
613
614 out += 1
615 end
616
617 output_length[0] = out
618
619 output.pack("U*")
620 end
621 (class <<self; private :punycode_decode; end)
622
623 def self.punycode_basic?(codepoint)
624 codepoint < 0x80
625 end
626 (class <<self; private :punycode_basic?; end)
627
628 def self.punycode_delimiter?(codepoint)
629 codepoint == PUNYCODE_DELIMITER
630 end
631 (class <<self; private :punycode_delimiter?; end)
632
633 def self.punycode_encode_digit(d)
634 d + 22 + 75 * ((d < 26) ? 1 : 0)
635 end
636 (class <<self; private :punycode_encode_digit; end)
637
638 # Returns the numeric value of a basic codepoint
639 # (for use in representing integers) in the range 0 to
640 # base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
641 def self.punycode_decode_digit(codepoint)
642 if codepoint - 48 < 10
643 codepoint - 22
644 elsif codepoint - 65 < 26
645 codepoint - 65
646 elsif codepoint - 97 < 26
647 codepoint - 97
648 else
649 PUNYCODE_BASE
650 end
651 end
652 (class <<self; private :punycode_decode_digit; end)
653
654 # Bias adaptation method
655 def self.punycode_adapt(delta, numpoints, firsttime)
656 delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1
657 # delta >> 1 is a faster way of doing delta / 2
658 delta += delta / numpoints
659 difference = PUNYCODE_BASE - PUNYCODE_TMIN
660
661 k = 0
662 while delta > (difference * PUNYCODE_TMAX) / 2
663 delta /= difference
664 k += PUNYCODE_BASE
665 end
666
667 k + (difference + 1) * delta / (delta + PUNYCODE_SKEW)
668 end
669 (class <<self; private :punycode_adapt; end)
670 end
671 # :startdoc:
672 end
+0
-25
vendor/addressable/idna.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2015 Bob Aman
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #++
16
17
18 begin
19 require "addressable/idna/native"
20 rescue LoadError
21 # libidn or the idn gem was not available, fall back on a pure-Ruby
22 # implementation...
23 require "addressable/idna/pure"
24 end
+0
-938
vendor/addressable/template.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2015 Bob Aman
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #++
16
17
18 require "addressable/version"
19 require "addressable/uri"
20
21 module Addressable
22 ##
23 # This is an implementation of a URI template based on
24 # RFC 6570 (http://tools.ietf.org/html/rfc6570).
25 class Template
26 # Constants used throughout the template code.
27 anything =
28 Addressable::URI::CharacterClasses::RESERVED +
29 Addressable::URI::CharacterClasses::UNRESERVED
30
31
32 variable_char_class =
33 Addressable::URI::CharacterClasses::ALPHA +
34 Addressable::URI::CharacterClasses::DIGIT + '_'
35
36 var_char =
37 "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
38 RESERVED =
39 "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
40 UNRESERVED =
41 "(?:[#{
42 Addressable::URI::CharacterClasses::UNRESERVED
43 }]|%[a-fA-F0-9][a-fA-F0-9])"
44 variable =
45 "(?:#{var_char}(?:\\.?#{var_char})*)"
46 varspec =
47 "(?:(#{variable})(\\*|:\\d+)?)"
48 VARNAME =
49 /^#{variable}$/
50 VARSPEC =
51 /^#{varspec}$/
52 VARIABLE_LIST =
53 /^#{varspec}(?:,#{varspec})*$/
54 operator =
55 "+#./;?&=,!@|"
56 EXPRESSION =
57 /\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
58
59
60 LEADERS = {
61 '?' => '?',
62 '/' => '/',
63 '#' => '#',
64 '.' => '.',
65 ';' => ';',
66 '&' => '&'
67 }
68 JOINERS = {
69 '?' => '&',
70 '.' => '.',
71 ';' => ';',
72 '&' => '&',
73 '/' => '/'
74 }
75
76 ##
77 # Raised if an invalid template value is supplied.
78 class InvalidTemplateValueError < StandardError
79 end
80
81 ##
82 # Raised if an invalid template operator is used in a pattern.
83 class InvalidTemplateOperatorError < StandardError
84 end
85
86 ##
87 # Raised if an invalid template operator is used in a pattern.
88 class TemplateOperatorAbortedError < StandardError
89 end
90
91 ##
92 # This class represents the data that is extracted when a Template
93 # is matched against a URI.
94 class MatchData
95 ##
96 # Creates a new MatchData object.
97 # MatchData objects should never be instantiated directly.
98 #
99 # @param [Addressable::URI] uri
100 # The URI that the template was matched against.
101 def initialize(uri, template, mapping)
102 @uri = uri.dup.freeze
103 @template = template
104 @mapping = mapping.dup.freeze
105 end
106
107 ##
108 # @return [Addressable::URI]
109 # The URI that the Template was matched against.
110 attr_reader :uri
111
112 ##
113 # @return [Addressable::Template]
114 # The Template used for the match.
115 attr_reader :template
116
117 ##
118 # @return [Hash]
119 # The mapping that resulted from the match.
120 # Note that this mapping does not include keys or values for
121 # variables that appear in the Template, but are not present
122 # in the URI.
123 attr_reader :mapping
124
125 ##
126 # @return [Array]
127 # The list of variables that were present in the Template.
128 # Note that this list will include variables which do not appear
129 # in the mapping because they were not present in URI.
130 def variables
131 self.template.variables
132 end
133 alias_method :keys, :variables
134 alias_method :names, :variables
135
136 ##
137 # @return [Array]
138 # The list of values that were captured by the Template.
139 # Note that this list will include nils for any variables which
140 # were in the Template, but did not appear in the URI.
141 def values
142 @values ||= self.variables.inject([]) do |accu, key|
143 accu << self.mapping[key]
144 accu
145 end
146 end
147 alias_method :captures, :values
148
149 ##
150 # Accesses captured values by name or by index.
151 #
152 # @param [String, Symbol, Fixnum] key
153 # Capture index or name. Note that when accessing by with index
154 # of 0, the full URI will be returned. The intention is to mimic
155 # the ::MatchData#[] behavior.
156 #
157 # @param [#to_int, nil] len
158 # If provided, an array of values will be returend with the given
159 # parameter used as length.
160 #
161 # @return [Array, String, nil]
162 # The captured value corresponding to the index or name. If the
163 # value was not provided or the key is unknown, nil will be
164 # returned.
165 #
166 # If the second parameter is provided, an array of that length will
167 # be returned instead.
168 def [](key, len = nil)
169 if len
170 to_a[key, len]
171 elsif String === key or Symbol === key
172 mapping[key.to_s]
173 else
174 to_a[key]
175 end
176 end
177
178 ##
179 # @return [Array]
180 # Array with the matched URI as first element followed by the captured
181 # values.
182 def to_a
183 [to_s, *values]
184 end
185
186 ##
187 # @return [String]
188 # The matched URI as String.
189 def to_s
190 uri.to_s
191 end
192 alias_method :string, :to_s
193
194 # Returns multiple captured values at once.
195 #
196 # @param [String, Symbol, Fixnum] *indexes
197 # Indices of the captures to be returned
198 #
199 # @return [Array]
200 # Values corresponding to given indices.
201 #
202 # @see Addressable::Template::MatchData#[]
203 def values_at(*indexes)
204 indexes.map { |i| self[i] }
205 end
206
207 ##
208 # Returns a <tt>String</tt> representation of the MatchData's state.
209 #
210 # @return [String] The MatchData's state, as a <tt>String</tt>.
211 def inspect
212 sprintf("#<%s:%#0x RESULT:%s>",
213 self.class.to_s, self.object_id, self.mapping.inspect)
214 end
215
216 ##
217 # Dummy method for code expecting a ::MatchData instance
218 #
219 # @return [String] An empty string.
220 def pre_match
221 ""
222 end
223 alias_method :post_match, :pre_match
224 end
225
226 ##
227 # Creates a new <tt>Addressable::Template</tt> object.
228 #
229 # @param [#to_str] pattern The URI Template pattern.
230 #
231 # @return [Addressable::Template] The initialized Template object.
232 def initialize(pattern)
233 if !pattern.respond_to?(:to_str)
234 raise TypeError, "Can't convert #{pattern.class} into String."
235 end
236 @pattern = pattern.to_str.freeze
237 end
238
239 ##
240 # @return [String] The Template object's pattern.
241 attr_reader :pattern
242
243 ##
244 # Returns a <tt>String</tt> representation of the Template object's state.
245 #
246 # @return [String] The Template object's state, as a <tt>String</tt>.
247 def inspect
248 sprintf("#<%s:%#0x PATTERN:%s>",
249 self.class.to_s, self.object_id, self.pattern)
250 end
251
252 ##
253 # Returns <code>true</code> if the Template objects are equal. This method
254 # does NOT normalize either Template before doing the comparison.
255 #
256 # @param [Object] template The Template to compare.
257 #
258 # @return [TrueClass, FalseClass]
259 # <code>true</code> if the Templates are equivalent, <code>false</code>
260 # otherwise.
261 def ==(template)
262 return false unless template.kind_of?(Template)
263 return self.pattern == template.pattern
264 end
265
266 ##
267 # Addressable::Template makes no distinction between `==` and `eql?`.
268 #
269 # @see #==
270 alias_method :eql?, :==
271
272 ##
273 # Extracts a mapping from the URI using a URI Template pattern.
274 #
275 # @param [Addressable::URI, #to_str] uri
276 # The URI to extract from.
277 #
278 # @param [#restore, #match] processor
279 # A template processor object may optionally be supplied.
280 #
281 # The object should respond to either the <tt>restore</tt> or
282 # <tt>match</tt> messages or both. The <tt>restore</tt> method should
283 # take two parameters: `[String] name` and `[String] value`.
284 # The <tt>restore</tt> method should reverse any transformations that
285 # have been performed on the value to ensure a valid URI.
286 # The <tt>match</tt> method should take a single
287 # parameter: `[String] name`. The <tt>match</tt> method should return
288 # a <tt>String</tt> containing a regular expression capture group for
289 # matching on that particular variable. The default value is `".*?"`.
290 # The <tt>match</tt> method has no effect on multivariate operator
291 # expansions.
292 #
293 # @return [Hash, NilClass]
294 # The <tt>Hash</tt> mapping that was extracted from the URI, or
295 # <tt>nil</tt> if the URI didn't match the template.
296 #
297 # @example
298 # class ExampleProcessor
299 # def self.restore(name, value)
300 # return value.gsub(/\+/, " ") if name == "query"
301 # return value
302 # end
303 #
304 # def self.match(name)
305 # return ".*?" if name == "first"
306 # return ".*"
307 # end
308 # end
309 #
310 # uri = Addressable::URI.parse(
311 # "http://example.com/search/an+example+search+query/"
312 # )
313 # Addressable::Template.new(
314 # "http://example.com/search/{query}/"
315 # ).extract(uri, ExampleProcessor)
316 # #=> {"query" => "an example search query"}
317 #
318 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
319 # Addressable::Template.new(
320 # "http://example.com/{first}/{second}/"
321 # ).extract(uri, ExampleProcessor)
322 # #=> {"first" => "a", "second" => "b/c"}
323 #
324 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
325 # Addressable::Template.new(
326 # "http://example.com/{first}/{-list|/|second}/"
327 # ).extract(uri)
328 # #=> {"first" => "a", "second" => ["b", "c"]}
329 def extract(uri, processor=nil)
330 match_data = self.match(uri, processor)
331 return (match_data ? match_data.mapping : nil)
332 end
333
334 ##
335 # Extracts match data from the URI using a URI Template pattern.
336 #
337 # @param [Addressable::URI, #to_str] uri
338 # The URI to extract from.
339 #
340 # @param [#restore, #match] processor
341 # A template processor object may optionally be supplied.
342 #
343 # The object should respond to either the <tt>restore</tt> or
344 # <tt>match</tt> messages or both. The <tt>restore</tt> method should
345 # take two parameters: `[String] name` and `[String] value`.
346 # The <tt>restore</tt> method should reverse any transformations that
347 # have been performed on the value to ensure a valid URI.
348 # The <tt>match</tt> method should take a single
349 # parameter: `[String] name`. The <tt>match</tt> method should return
350 # a <tt>String</tt> containing a regular expression capture group for
351 # matching on that particular variable. The default value is `".*?"`.
352 # The <tt>match</tt> method has no effect on multivariate operator
353 # expansions.
354 #
355 # @return [Hash, NilClass]
356 # The <tt>Hash</tt> mapping that was extracted from the URI, or
357 # <tt>nil</tt> if the URI didn't match the template.
358 #
359 # @example
360 # class ExampleProcessor
361 # def self.restore(name, value)
362 # return value.gsub(/\+/, " ") if name == "query"
363 # return value
364 # end
365 #
366 # def self.match(name)
367 # return ".*?" if name == "first"
368 # return ".*"
369 # end
370 # end
371 #
372 # uri = Addressable::URI.parse(
373 # "http://example.com/search/an+example+search+query/"
374 # )
375 # match = Addressable::Template.new(
376 # "http://example.com/search/{query}/"
377 # ).match(uri, ExampleProcessor)
378 # match.variables
379 # #=> ["query"]
380 # match.captures
381 # #=> ["an example search query"]
382 #
383 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
384 # match = Addressable::Template.new(
385 # "http://example.com/{first}/{+second}/"
386 # ).match(uri, ExampleProcessor)
387 # match.variables
388 # #=> ["first", "second"]
389 # match.captures
390 # #=> ["a", "b/c"]
391 #
392 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
393 # match = Addressable::Template.new(
394 # "http://example.com/{first}{/second*}/"
395 # ).match(uri)
396 # match.variables
397 # #=> ["first", "second"]
398 # match.captures
399 # #=> ["a", ["b", "c"]]
400 def match(uri, processor=nil)
401 uri = Addressable::URI.parse(uri)
402 mapping = {}
403
404 # First, we need to process the pattern, and extract the values.
405 expansions, expansion_regexp =
406 parse_template_pattern(pattern, processor)
407
408 return nil unless uri.to_str.match(expansion_regexp)
409 unparsed_values = uri.to_str.scan(expansion_regexp).flatten
410
411 if uri.to_str == pattern
412 return Addressable::Template::MatchData.new(uri, self, mapping)
413 elsif expansions.size > 0
414 index = 0
415 expansions.each do |expansion|
416 _, operator, varlist = *expansion.match(EXPRESSION)
417 varlist.split(',').each do |varspec|
418 _, name, modifier = *varspec.match(VARSPEC)
419 mapping[name] ||= nil
420 case operator
421 when nil, '+', '#', '/', '.'
422 unparsed_value = unparsed_values[index]
423 name = varspec[VARSPEC, 1]
424 value = unparsed_value
425 value = value.split(JOINERS[operator]) if value && modifier == '*'
426 when ';', '?', '&'
427 if modifier == '*'
428 if unparsed_values[index]
429 value = unparsed_values[index].split(JOINERS[operator])
430 value = value.inject({}) do |acc, v|
431 key, val = v.split('=')
432 val = "" if val.nil?
433 acc[key] = val
434 acc
435 end
436 end
437 else
438 if (unparsed_values[index])
439 name, value = unparsed_values[index].split('=')
440 value = "" if value.nil?
441 end
442 end
443 end
444 if processor != nil && processor.respond_to?(:restore)
445 value = processor.restore(name, value)
446 end
447 if processor == nil
448 if value.is_a?(Hash)
449 value = value.inject({}){|acc, (k, v)|
450 acc[Addressable::URI.unencode_component(k)] =
451 Addressable::URI.unencode_component(v)
452 acc
453 }
454 elsif value.is_a?(Array)
455 value = value.map{|v| Addressable::URI.unencode_component(v) }
456 else
457 value = Addressable::URI.unencode_component(value)
458 end
459 end
460 if !mapping.has_key?(name) || mapping[name].nil?
461 # Doesn't exist, set to value (even if value is nil)
462 mapping[name] = value
463 end
464 index = index + 1
465 end
466 end
467 return Addressable::Template::MatchData.new(uri, self, mapping)
468 else
469 return nil
470 end
471 end
472
473 ##
474 # Expands a URI template into another URI template.
475 #
476 # @param [Hash] mapping The mapping that corresponds to the pattern.
477 # @param [#validate, #transform] processor
478 # An optional processor object may be supplied.
479 #
480 # The object should respond to either the <tt>validate</tt> or
481 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
482 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
483 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
484 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
485 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
486 # exception will be raised if the value is invalid. The <tt>transform</tt>
487 # method should return the transformed variable value as a <tt>String</tt>.
488 # If a <tt>transform</tt> method is used, the value will not be percent
489 # encoded automatically. Unicode normalization will be performed both
490 # before and after sending the value to the transform method.
491 #
492 # @return [Addressable::Template] The partially expanded URI template.
493 #
494 # @example
495 # Addressable::Template.new(
496 # "http://example.com/{one}/{two}/"
497 # ).partial_expand({"one" => "1"}).pattern
498 # #=> "http://example.com/1/{two}/"
499 #
500 # Addressable::Template.new(
501 # "http://example.com/{?one,two}/"
502 # ).partial_expand({"one" => "1"}).pattern
503 # #=> "http://example.com/?one=1{&two}/"
504 #
505 # Addressable::Template.new(
506 # "http://example.com/{?one,two,three}/"
507 # ).partial_expand({"one" => "1", "three" => 3}).pattern
508 # #=> "http://example.com/?one=1{&two}&three=3"
509 def partial_expand(mapping, processor=nil)
510 result = self.pattern.dup
511 mapping = normalize_keys(mapping)
512 result.gsub!( EXPRESSION ) do |capture|
513 transform_partial_capture(mapping, capture, processor)
514 end
515 return Addressable::Template.new(result)
516 end
517
518 ##
519 # Expands a URI template into a full URI.
520 #
521 # @param [Hash] mapping The mapping that corresponds to the pattern.
522 # @param [#validate, #transform] processor
523 # An optional processor object may be supplied.
524 #
525 # The object should respond to either the <tt>validate</tt> or
526 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
527 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
528 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
529 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
530 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
531 # exception will be raised if the value is invalid. The <tt>transform</tt>
532 # method should return the transformed variable value as a <tt>String</tt>.
533 # If a <tt>transform</tt> method is used, the value will not be percent
534 # encoded automatically. Unicode normalization will be performed both
535 # before and after sending the value to the transform method.
536 #
537 # @return [Addressable::URI] The expanded URI template.
538 #
539 # @example
540 # class ExampleProcessor
541 # def self.validate(name, value)
542 # return !!(value =~ /^[\w ]+$/) if name == "query"
543 # return true
544 # end
545 #
546 # def self.transform(name, value)
547 # return value.gsub(/ /, "+") if name == "query"
548 # return value
549 # end
550 # end
551 #
552 # Addressable::Template.new(
553 # "http://example.com/search/{query}/"
554 # ).expand(
555 # {"query" => "an example search query"},
556 # ExampleProcessor
557 # ).to_str
558 # #=> "http://example.com/search/an+example+search+query/"
559 #
560 # Addressable::Template.new(
561 # "http://example.com/search/{query}/"
562 # ).expand(
563 # {"query" => "an example search query"}
564 # ).to_str
565 # #=> "http://example.com/search/an%20example%20search%20query/"
566 #
567 # Addressable::Template.new(
568 # "http://example.com/search/{query}/"
569 # ).expand(
570 # {"query" => "bogus!"},
571 # ExampleProcessor
572 # ).to_str
573 # #=> Addressable::Template::InvalidTemplateValueError
574 def expand(mapping, processor=nil)
575 result = self.pattern.dup
576 mapping = normalize_keys(mapping)
577 result.gsub!( EXPRESSION ) do |capture|
578 transform_capture(mapping, capture, processor)
579 end
580 return Addressable::URI.parse(result)
581 end
582
583 ##
584 # Returns an Array of variables used within the template pattern.
585 # The variables are listed in the Array in the order they appear within
586 # the pattern. Multiple occurrences of a variable within a pattern are
587 # not represented in this Array.
588 #
589 # @return [Array] The variables present in the template's pattern.
590 def variables
591 @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
592 end
593 alias_method :keys, :variables
594
595 ##
596 # Returns a mapping of variables to their default values specified
597 # in the template. Variables without defaults are not returned.
598 #
599 # @return [Hash] Mapping of template variables to their defaults
600 def variable_defaults
601 @variable_defaults ||=
602 Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
603 end
604
605 private
606 def ordered_variable_defaults
607 @ordered_variable_defaults ||= (
608 expansions, _ = parse_template_pattern(pattern)
609 expansions.map do |capture|
610 _, _, varlist = *capture.match(EXPRESSION)
611 varlist.split(',').map do |varspec|
612 varspec[VARSPEC, 1]
613 end
614 end.flatten
615 )
616 end
617
618
619 ##
620 # Loops through each capture and expands any values available in mapping
621 #
622 # @param [Hash] mapping
623 # Set of keys to expand
624 # @param [String] capture
625 # The expression to expand
626 # @param [#validate, #transform] processor
627 # An optional processor object may be supplied.
628 #
629 # The object should respond to either the <tt>validate</tt> or
630 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
631 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
632 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
633 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
634 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
635 # will be raised if the value is invalid. The <tt>transform</tt> method
636 # should return the transformed variable value as a <tt>String</tt>. If a
637 # <tt>transform</tt> method is used, the value will not be percent encoded
638 # automatically. Unicode normalization will be performed both before and
639 # after sending the value to the transform method.
640 #
641 # @return [String] The expanded expression
642 def transform_partial_capture(mapping, capture, processor = nil)
643 _, operator, varlist = *capture.match(EXPRESSION)
644 is_first = true
645 varlist.split(',').inject('') do |acc, varspec|
646 _, name, _ = *varspec.match(VARSPEC)
647 value = mapping[name]
648 if value
649 operator = '&' if !is_first && operator == '?'
650 acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
651 else
652 operator = '&' if !is_first && operator == '?'
653 acc << "{#{operator}#{varspec}}"
654 end
655 is_first = false
656 acc
657 end
658 end
659
660 ##
661 # Transforms a mapped value so that values can be substituted into the
662 # template.
663 #
664 # @param [Hash] mapping The mapping to replace captures
665 # @param [String] capture
666 # The expression to replace
667 # @param [#validate, #transform] processor
668 # An optional processor object may be supplied.
669 #
670 # The object should respond to either the <tt>validate</tt> or
671 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
672 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
673 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
674 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
675 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
676 # will be raised if the value is invalid. The <tt>transform</tt> method
677 # should return the transformed variable value as a <tt>String</tt>. If a
678 # <tt>transform</tt> method is used, the value will not be percent encoded
679 # automatically. Unicode normalization will be performed both before and
680 # after sending the value to the transform method.
681 #
682 # @return [String] The expanded expression
683 def transform_capture(mapping, capture, processor=nil)
684 _, operator, varlist = *capture.match(EXPRESSION)
685 return_value = varlist.split(',').inject([]) do |acc, varspec|
686 _, name, modifier = *varspec.match(VARSPEC)
687 value = mapping[name]
688 unless value == nil || value == {}
689 allow_reserved = %w(+ #).include?(operator)
690 # Common primitives where the .to_s output is well-defined
691 if Numeric === value || Symbol === value ||
692 value == true || value == false
693 value = value.to_s
694 end
695 length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
696
697 unless (Hash === value) ||
698 value.respond_to?(:to_ary) || value.respond_to?(:to_str)
699 raise TypeError,
700 "Can't convert #{value.class} into String or Array."
701 end
702
703 value = normalize_value(value)
704
705 if processor == nil || !processor.respond_to?(:transform)
706 # Handle percent escaping
707 if allow_reserved
708 encode_map =
709 Addressable::URI::CharacterClasses::RESERVED +
710 Addressable::URI::CharacterClasses::UNRESERVED
711 else
712 encode_map = Addressable::URI::CharacterClasses::UNRESERVED
713 end
714 if value.kind_of?(Array)
715 transformed_value = value.map do |val|
716 if length
717 Addressable::URI.encode_component(val[0...length], encode_map)
718 else
719 Addressable::URI.encode_component(val, encode_map)
720 end
721 end
722 unless modifier == "*"
723 transformed_value = transformed_value.join(',')
724 end
725 elsif value.kind_of?(Hash)
726 transformed_value = value.map do |key, val|
727 if modifier == "*"
728 "#{
729 Addressable::URI.encode_component( key, encode_map)
730 }=#{
731 Addressable::URI.encode_component( val, encode_map)
732 }"
733 else
734 "#{
735 Addressable::URI.encode_component( key, encode_map)
736 },#{
737 Addressable::URI.encode_component( val, encode_map)
738 }"
739 end
740 end
741 unless modifier == "*"
742 transformed_value = transformed_value.join(',')
743 end
744 else
745 if length
746 transformed_value = Addressable::URI.encode_component(
747 value[0...length], encode_map)
748 else
749 transformed_value = Addressable::URI.encode_component(
750 value, encode_map)
751 end
752 end
753 end
754
755 # Process, if we've got a processor
756 if processor != nil
757 if processor.respond_to?(:validate)
758 if !processor.validate(name, value)
759 display_value = value.kind_of?(Array) ? value.inspect : value
760 raise InvalidTemplateValueError,
761 "#{name}=#{display_value} is an invalid template value."
762 end
763 end
764 if processor.respond_to?(:transform)
765 transformed_value = processor.transform(name, value)
766 transformed_value = normalize_value(transformed_value)
767 end
768 end
769 acc << [name, transformed_value]
770 end
771 acc
772 end
773 return "" if return_value.empty?
774 join_values(operator, return_value)
775 end
776
777 ##
778 # Takes a set of values, and joins them together based on the
779 # operator.
780 #
781 # @param [String, Nil] operator One of the operators from the set
782 # (?,&,+,#,;,/,.), or nil if there wasn't one.
783 # @param [Array] return_value
784 # The set of return values (as [variable_name, value] tuples) that will
785 # be joined together.
786 #
787 # @return [String] The transformed mapped value
788 def join_values(operator, return_value)
789 leader = LEADERS.fetch(operator, '')
790 joiner = JOINERS.fetch(operator, ',')
791 case operator
792 when '&', '?'
793 leader + return_value.map{|k,v|
794 if v.is_a?(Array) && v.first =~ /=/
795 v.join(joiner)
796 elsif v.is_a?(Array)
797 v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
798 else
799 "#{k}=#{v}"
800 end
801 }.join(joiner)
802 when ';'
803 return_value.map{|k,v|
804 if v.is_a?(Array) && v.first =~ /=/
805 ';' + v.join(";")
806 elsif v.is_a?(Array)
807 ';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
808 else
809 v && v != '' ? ";#{k}=#{v}" : ";#{k}"
810 end
811 }.join
812 else
813 leader + return_value.map{|k,v| v}.join(joiner)
814 end
815 end
816
817 ##
818 # Takes a set of values, and joins them together based on the
819 # operator.
820 #
821 # @param [Hash, Array, String] value
822 # Normalizes keys and values with IDNA#unicode_normalize_kc
823 #
824 # @return [Hash, Array, String] The normalized values
825 def normalize_value(value)
826 unless value.is_a?(Hash)
827 value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
828 end
829
830 # Handle unicode normalization
831 if value.kind_of?(Array)
832 value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
833 elsif value.kind_of?(Hash)
834 value = value.inject({}) { |acc, (k, v)|
835 acc[Addressable::IDNA.unicode_normalize_kc(k)] =
836 Addressable::IDNA.unicode_normalize_kc(v)
837 acc
838 }
839 else
840 value = Addressable::IDNA.unicode_normalize_kc(value)
841 end
842 value
843 end
844
845 ##
846 # Generates a hash with string keys
847 #
848 # @param [Hash] mapping A mapping hash to normalize
849 #
850 # @return [Hash]
851 # A hash with stringified keys
852 def normalize_keys(mapping)
853 return mapping.inject({}) do |accu, pair|
854 name, value = pair
855 if Symbol === name
856 name = name.to_s
857 elsif name.respond_to?(:to_str)
858 name = name.to_str
859 else
860 raise TypeError,
861 "Can't convert #{name.class} into String."
862 end
863 accu[name] = value
864 accu
865 end
866 end
867
868 ##
869 # Generates the <tt>Regexp</tt> that parses a template pattern.
870 #
871 # @param [String] pattern The URI template pattern.
872 # @param [#match] processor The template processor to use.
873 #
874 # @return [Regexp]
875 # A regular expression which may be used to parse a template pattern.
876 def parse_template_pattern(pattern, processor=nil)
877 # Escape the pattern. The two gsubs restore the escaped curly braces
878 # back to their original form. Basically, escape everything that isn't
879 # within an expansion.
880 escaped_pattern = Regexp.escape(
881 pattern
882 ).gsub(/\\\{(.*?)\\\}/) do |escaped|
883 escaped.gsub(/\\(.)/, "\\1")
884 end
885
886 expansions = []
887
888 # Create a regular expression that captures the values of the
889 # variables in the URI.
890 regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
891
892 expansions << expansion
893 _, operator, varlist = *expansion.match(EXPRESSION)
894 leader = Regexp.escape(LEADERS.fetch(operator, ''))
895 joiner = Regexp.escape(JOINERS.fetch(operator, ','))
896 combined = varlist.split(',').map do |varspec|
897 _, name, modifier = *varspec.match(VARSPEC)
898
899 result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
900 if result
901 "(#{ result })"
902 else
903 group = case operator
904 when '+'
905 "#{ RESERVED }*?"
906 when '#'
907 "#{ RESERVED }*?"
908 when '/'
909 "#{ UNRESERVED }*?"
910 when '.'
911 "#{ UNRESERVED.gsub('\.', '') }*?"
912 when ';'
913 "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
914 when '?'
915 "#{ UNRESERVED }*=#{ UNRESERVED }*?"
916 when '&'
917 "#{ UNRESERVED }*=#{ UNRESERVED }*?"
918 else
919 "#{ UNRESERVED }*?"
920 end
921 if modifier == '*'
922 "(#{group}(?:#{joiner}?#{group})*)?"
923 else
924 "(#{group})?"
925 end
926 end
927 end.join("#{joiner}?")
928 "(?:|#{leader}#{combined})"
929 end
930
931 # Ensure that the regular expression matches the whole URI.
932 regexp_string = "^#{regexp_string}$"
933 return expansions, Regexp.new(regexp_string)
934 end
935
936 end
937 end
+0
-2361
vendor/addressable/uri.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2015 Bob Aman
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #++
16
17
18 require "addressable/version"
19 require "addressable/idna"
20
21 if RUBY_VERSION =~ /^1.8/
22 warn('Support for Ruby 1.8.x in Addressable is deprecated.')
23 end
24
25 ##
26 # Addressable is a library for processing links and URIs.
27 module Addressable
28 ##
29 # This is an implementation of a URI parser based on
30 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
31 # <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
32 class URI
33 ##
34 # Raised if something other than a uri is supplied.
35 class InvalidURIError < StandardError
36 end
37
38 ##
39 # Container for the character classes specified in
40 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
41 module CharacterClasses
42 ALPHA = "a-zA-Z"
43 DIGIT = "0-9"
44 GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
45 SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
46 RESERVED = GEN_DELIMS + SUB_DELIMS
47 UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
48 PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
49 SCHEME = ALPHA + DIGIT + "\\-\\+\\."
50 AUTHORITY = PCHAR
51 PATH = PCHAR + "\\/"
52 QUERY = PCHAR + "\\/\\?"
53 FRAGMENT = PCHAR + "\\/\\?"
54 end
55
56 SLASH = '/'
57 EMPTY_STR = ''
58
59 URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
60
61 PORT_MAPPING = {
62 "http" => 80,
63 "https" => 443,
64 "ftp" => 21,
65 "tftp" => 69,
66 "sftp" => 22,
67 "ssh" => 22,
68 "svn+ssh" => 22,
69 "telnet" => 23,
70 "nntp" => 119,
71 "gopher" => 70,
72 "wais" => 210,
73 "ldap" => 389,
74 "prospero" => 1525
75 }
76
77 ##
78 # Returns a URI object based on the parsed string.
79 #
80 # @param [String, Addressable::URI, #to_str] uri
81 # The URI string to parse.
82 # No parsing is performed if the object is already an
83 # <code>Addressable::URI</code>.
84 #
85 # @return [Addressable::URI] The parsed URI.
86 def self.parse(uri)
87 # If we were given nil, return nil.
88 return nil unless uri
89 # If a URI object is passed, just return itself.
90 return uri.dup if uri.kind_of?(self)
91
92 # If a URI object of the Ruby standard library variety is passed,
93 # convert it to a string, then parse the string.
94 # We do the check this way because we don't want to accidentally
95 # cause a missing constant exception to be thrown.
96 if uri.class.name =~ /^URI\b/
97 uri = uri.to_s
98 end
99
100 # Otherwise, convert to a String
101 begin
102 uri = uri.to_str
103 rescue TypeError, NoMethodError
104 raise TypeError, "Can't convert #{uri.class} into String."
105 end if not uri.is_a? String
106
107 # This Regexp supplied as an example in RFC 3986, and it works great.
108 scan = uri.scan(URIREGEX)
109 fragments = scan[0]
110 scheme = fragments[1]
111 authority = fragments[3]
112 path = fragments[4]
113 query = fragments[6]
114 fragment = fragments[8]
115 user = nil
116 password = nil
117 host = nil
118 port = nil
119 if authority != nil
120 # The Regexp above doesn't split apart the authority.
121 userinfo = authority[/^([^\[\]]*)@/, 1]
122 if userinfo != nil
123 user = userinfo.strip[/^([^:]*):?/, 1]
124 password = userinfo.strip[/:(.*)$/, 1]
125 end
126 host = authority.gsub(
127 /^([^\[\]]*)@/, EMPTY_STR
128 ).gsub(
129 /:([^:@\[\]]*?)$/, EMPTY_STR
130 )
131 port = authority[/:([^:@\[\]]*?)$/, 1]
132 end
133 if port == EMPTY_STR
134 port = nil
135 end
136
137 return new(
138 :scheme => scheme,
139 :user => user,
140 :password => password,
141 :host => host,
142 :port => port,
143 :path => path,
144 :query => query,
145 :fragment => fragment
146 )
147 end
148
149 ##
150 # Converts an input to a URI. The input does not have to be a valid
151 # URI — the method will use heuristics to guess what URI was intended.
152 # This is not standards-compliant, merely user-friendly.
153 #
154 # @param [String, Addressable::URI, #to_str] uri
155 # The URI string to parse.
156 # No parsing is performed if the object is already an
157 # <code>Addressable::URI</code>.
158 # @param [Hash] hints
159 # A <code>Hash</code> of hints to the heuristic parser.
160 # Defaults to <code>{:scheme => "http"}</code>.
161 #
162 # @return [Addressable::URI] The parsed URI.
163 def self.heuristic_parse(uri, hints={})
164 # If we were given nil, return nil.
165 return nil unless uri
166 # If a URI object is passed, just return itself.
167 return uri.dup if uri.kind_of?(self)
168
169 # If a URI object of the Ruby standard library variety is passed,
170 # convert it to a string, then parse the string.
171 # We do the check this way because we don't want to accidentally
172 # cause a missing constant exception to be thrown.
173 if uri.class.name =~ /^URI\b/
174 uri = uri.to_s
175 end
176
177 if !uri.respond_to?(:to_str)
178 raise TypeError, "Can't convert #{uri.class} into String."
179 end
180 # Otherwise, convert to a String
181 uri = uri.to_str.dup
182 hints = {
183 :scheme => "http"
184 }.merge(hints)
185 case uri
186 when /^http:\/+/
187 uri.gsub!(/^http:\/+/, "http://")
188 when /^https:\/+/
189 uri.gsub!(/^https:\/+/, "https://")
190 when /^feed:\/+http:\/+/
191 uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
192 when /^feed:\/+/
193 uri.gsub!(/^feed:\/+/, "feed://")
194 when /^file:\/+/
195 uri.gsub!(/^file:\/+/, "file:///")
196 when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
197 uri.gsub!(/^/, hints[:scheme] + "://")
198 end
199 parsed = self.parse(uri)
200 if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
201 parsed = self.parse(hints[:scheme] + "://" + uri)
202 end
203 if parsed.path.include?(".")
204 new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
205 if new_host
206 parsed.defer_validation do
207 new_path = parsed.path.gsub(
208 Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
209 parsed.host = new_host
210 parsed.path = new_path
211 parsed.scheme = hints[:scheme] unless parsed.scheme
212 end
213 end
214 end
215 return parsed
216 end
217
218 ##
219 # Converts a path to a file scheme URI. If the path supplied is
220 # relative, it will be returned as a relative URI. If the path supplied
221 # is actually a non-file URI, it will parse the URI as if it had been
222 # parsed with <code>Addressable::URI.parse</code>. Handles all of the
223 # various Microsoft-specific formats for specifying paths.
224 #
225 # @param [String, Addressable::URI, #to_str] path
226 # Typically a <code>String</code> path to a file or directory, but
227 # will return a sensible return value if an absolute URI is supplied
228 # instead.
229 #
230 # @return [Addressable::URI]
231 # The parsed file scheme URI or the original URI if some other URI
232 # scheme was provided.
233 #
234 # @example
235 # base = Addressable::URI.convert_path("/absolute/path/")
236 # uri = Addressable::URI.convert_path("relative/path")
237 # (base + uri).to_s
238 # #=> "file:///absolute/path/relative/path"
239 #
240 # Addressable::URI.convert_path(
241 # "c:\\windows\\My Documents 100%20\\foo.txt"
242 # ).to_s
243 # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
244 #
245 # Addressable::URI.convert_path("http://example.com/").to_s
246 # #=> "http://example.com/"
247 def self.convert_path(path)
248 # If we were given nil, return nil.
249 return nil unless path
250 # If a URI object is passed, just return itself.
251 return path if path.kind_of?(self)
252 if !path.respond_to?(:to_str)
253 raise TypeError, "Can't convert #{path.class} into String."
254 end
255 # Otherwise, convert to a String
256 path = path.to_str.strip
257
258 path.gsub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
259 path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
260 uri = self.parse(path)
261
262 if uri.scheme == nil
263 # Adjust windows-style uris
264 uri.path.gsub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
265 "/#{$1.downcase}:/"
266 end
267 uri.path.gsub!(/\\/, SLASH)
268 if File.exist?(uri.path) &&
269 File.stat(uri.path).directory?
270 uri.path.gsub!(/\/$/, EMPTY_STR)
271 uri.path = uri.path + '/'
272 end
273
274 # If the path is absolute, set the scheme and host.
275 if uri.path =~ /^\//
276 uri.scheme = "file"
277 uri.host = EMPTY_STR
278 end
279 uri.normalize!
280 end
281
282 return uri
283 end
284
285 ##
286 # Joins several URIs together.
287 #
288 # @param [String, Addressable::URI, #to_str] *uris
289 # The URIs to join.
290 #
291 # @return [Addressable::URI] The joined URI.
292 #
293 # @example
294 # base = "http://example.com/"
295 # uri = Addressable::URI.parse("relative/path")
296 # Addressable::URI.join(base, uri)
297 # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
298 def self.join(*uris)
299 uri_objects = uris.collect do |uri|
300 if !uri.respond_to?(:to_str)
301 raise TypeError, "Can't convert #{uri.class} into String."
302 end
303 uri.kind_of?(self) ? uri : self.parse(uri.to_str)
304 end
305 result = uri_objects.shift.dup
306 for uri in uri_objects
307 result.join!(uri)
308 end
309 return result
310 end
311
312 ##
313 # Percent encodes a URI component.
314 #
315 # @param [String, #to_str] component The URI component to encode.
316 #
317 # @param [String, Regexp] character_class
318 # The characters which are not percent encoded. If a <code>String</code>
319 # is passed, the <code>String</code> must be formatted as a regular
320 # expression character class. (Do not include the surrounding square
321 # brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
322 # everything but the letters 'b' through 'z' and the numbers '0' through
323 # '9' to be percent encoded. If a <code>Regexp</code> is passed, the
324 # value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A set of
325 # useful <code>String</code> values may be found in the
326 # <code>Addressable::URI::CharacterClasses</code> module. The default
327 # value is the reserved plus unreserved character classes specified in
328 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
329 #
330 # @param [Regexp] upcase_encoded
331 # A string of characters that may already be percent encoded, and whose
332 # encodings should be upcased. This allows normalization of percent
333 # encodings for characters not included in the
334 # <code>character_class</code>.
335 #
336 # @return [String] The encoded component.
337 #
338 # @example
339 # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
340 # => "simple%2Fex%61mple"
341 # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
342 # => "simple%2Fex%61mple"
343 # Addressable::URI.encode_component(
344 # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
345 # )
346 # => "simple%2Fexample"
347 def self.encode_component(component, character_class=
348 CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
349 upcase_encoded='')
350 return nil if component.nil?
351
352 begin
353 if component.kind_of?(Symbol) ||
354 component.kind_of?(Numeric) ||
355 component.kind_of?(TrueClass) ||
356 component.kind_of?(FalseClass)
357 component = component.to_s
358 else
359 component = component.to_str
360 end
361 rescue TypeError, NoMethodError
362 raise TypeError, "Can't convert #{component.class} into String."
363 end if !component.is_a? String
364
365 if ![String, Regexp].include?(character_class.class)
366 raise TypeError,
367 "Expected String or Regexp, got #{character_class.inspect}"
368 end
369 if character_class.kind_of?(String)
370 character_class = /[^#{character_class}]/
371 end
372 if component.respond_to?(:force_encoding)
373 # We can't perform regexps on invalid UTF sequences, but
374 # here we need to, so switch to ASCII.
375 component = component.dup
376 component.force_encoding(Encoding::ASCII_8BIT)
377 end
378 # Avoiding gsub! because there are edge cases with frozen strings
379 component = component.gsub(character_class) do |sequence|
380 (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
381 end
382 if upcase_encoded.length > 0
383 component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
384 char.unpack('C*').map { |c| '%02x' % c }.join
385 end.join('|')})/i) { |s| s.upcase }
386 end
387 return component
388 end
389
390 class << self
391 alias_method :encode_component, :encode_component
392 end
393
394 ##
395 # Unencodes any percent encoded characters within a URI component.
396 # This method may be used for unencoding either components or full URIs,
397 # however, it is recommended to use the <code>unencode_component</code>
398 # alias when unencoding components.
399 #
400 # @param [String, Addressable::URI, #to_str] uri
401 # The URI or component to unencode.
402 #
403 # @param [Class] return_type
404 # The type of object to return.
405 # This value may only be set to <code>String</code> or
406 # <code>Addressable::URI</code>. All other values are invalid. Defaults
407 # to <code>String</code>.
408 #
409 # @param [String] leave_encoded
410 # A string of characters to leave encoded. If a percent encoded character
411 # in this list is encountered then it will remain percent encoded.
412 #
413 # @return [String, Addressable::URI]
414 # The unencoded component or URI.
415 # The return type is determined by the <code>return_type</code>
416 # parameter.
417 def self.unencode(uri, return_type=String, leave_encoded='')
418 return nil if uri.nil?
419
420 begin
421 uri = uri.to_str
422 rescue NoMethodError, TypeError
423 raise TypeError, "Can't convert #{uri.class} into String."
424 end if !uri.is_a? String
425 if ![String, ::Addressable::URI].include?(return_type)
426 raise TypeError,
427 "Expected Class (String or Addressable::URI), " +
428 "got #{return_type.inspect}"
429 end
430 uri = uri.dup
431 # Seriously, only use UTF-8. I'm really not kidding!
432 uri.force_encoding("utf-8") if uri.respond_to?(:force_encoding)
433 leave_encoded.force_encoding("utf-8") if leave_encoded.respond_to?(:force_encoding)
434 result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
435 c = sequence[1..3].to_i(16).chr
436 c.force_encoding("utf-8") if c.respond_to?(:force_encoding)
437 leave_encoded.include?(c) ? sequence : c
438 end
439 result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
440 if return_type == String
441 return result
442 elsif return_type == ::Addressable::URI
443 return ::Addressable::URI.parse(result)
444 end
445 end
446
447 class << self
448 alias_method :unescape, :unencode
449 alias_method :unencode_component, :unencode
450 alias_method :unescape_component, :unencode
451 end
452
453
454 ##
455 # Normalizes the encoding of a URI component.
456 #
457 # @param [String, #to_str] component The URI component to encode.
458 #
459 # @param [String, Regexp] character_class
460 # The characters which are not percent encoded. If a <code>String</code>
461 # is passed, the <code>String</code> must be formatted as a regular
462 # expression character class. (Do not include the surrounding square
463 # brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
464 # everything but the letters 'b' through 'z' and the numbers '0'
465 # through '9' to be percent encoded. If a <code>Regexp</code> is passed,
466 # the value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
467 # set of useful <code>String</code> values may be found in the
468 # <code>Addressable::URI::CharacterClasses</code> module. The default
469 # value is the reserved plus unreserved character classes specified in
470 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
471 #
472 # @param [String] leave_encoded
473 # When <code>character_class</code> is a <code>String</code> then
474 # <code>leave_encoded</code> is a string of characters that should remain
475 # percent encoded while normalizing the component; if they appear percent
476 # encoded in the original component, then they will be upcased ("%2f"
477 # normalized to "%2F") but otherwise left alone.
478 #
479 # @return [String] The normalized component.
480 #
481 # @example
482 # Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z")
483 # => "simple%2Fex%61mple"
484 # Addressable::URI.normalize_component(
485 # "simpl%65/%65xampl%65", /[^b-zB-Z]/
486 # )
487 # => "simple%2Fex%61mple"
488 # Addressable::URI.normalize_component(
489 # "simpl%65/%65xampl%65",
490 # Addressable::URI::CharacterClasses::UNRESERVED
491 # )
492 # => "simple%2Fexample"
493 # Addressable::URI.normalize_component(
494 # "one%20two%2fthree%26four",
495 # "0-9a-zA-Z &/",
496 # "/"
497 # )
498 # => "one two%2Fthree&four"
499 def self.normalize_component(component, character_class=
500 CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
501 leave_encoded='')
502 return nil if component.nil?
503
504 begin
505 component = component.to_str
506 rescue NoMethodError, TypeError
507 raise TypeError, "Can't convert #{component.class} into String."
508 end if !component.is_a? String
509
510 if ![String, Regexp].include?(character_class.class)
511 raise TypeError,
512 "Expected String or Regexp, got #{character_class.inspect}"
513 end
514 if character_class.kind_of?(String)
515 leave_re = if leave_encoded.length > 0
516 character_class = "#{character_class}%" unless character_class.include?('%')
517
518 "|%(?!#{leave_encoded.chars.map do |char|
519 seq = char.unpack('C*').map { |c| '%02x' % c }.join
520 [seq.upcase, seq.downcase]
521 end.flatten.join('|')})"
522 end
523
524 character_class = /[^#{character_class}]#{leave_re}/
525 end
526 if component.respond_to?(:force_encoding)
527 # We can't perform regexps on invalid UTF sequences, but
528 # here we need to, so switch to ASCII.
529 component = component.dup
530 component.force_encoding(Encoding::ASCII_8BIT)
531 end
532 unencoded = self.unencode_component(component, String, leave_encoded)
533 begin
534 encoded = self.encode_component(
535 Addressable::IDNA.unicode_normalize_kc(unencoded),
536 character_class,
537 leave_encoded
538 )
539 rescue ArgumentError
540 encoded = self.encode_component(unencoded)
541 end
542 if encoded.respond_to?(:force_encoding)
543 encoded.force_encoding(Encoding::UTF_8)
544 end
545 return encoded
546 end
547
548 ##
549 # Percent encodes any special characters in the URI.
550 #
551 # @param [String, Addressable::URI, #to_str] uri
552 # The URI to encode.
553 #
554 # @param [Class] return_type
555 # The type of object to return.
556 # This value may only be set to <code>String</code> or
557 # <code>Addressable::URI</code>. All other values are invalid. Defaults
558 # to <code>String</code>.
559 #
560 # @return [String, Addressable::URI]
561 # The encoded URI.
562 # The return type is determined by the <code>return_type</code>
563 # parameter.
564 def self.encode(uri, return_type=String)
565 return nil if uri.nil?
566
567 begin
568 uri = uri.to_str
569 rescue NoMethodError, TypeError
570 raise TypeError, "Can't convert #{uri.class} into String."
571 end if !uri.is_a? String
572
573 if ![String, ::Addressable::URI].include?(return_type)
574 raise TypeError,
575 "Expected Class (String or Addressable::URI), " +
576 "got #{return_type.inspect}"
577 end
578 uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
579 encoded_uri = Addressable::URI.new(
580 :scheme => self.encode_component(uri_object.scheme,
581 Addressable::URI::CharacterClasses::SCHEME),
582 :authority => self.encode_component(uri_object.authority,
583 Addressable::URI::CharacterClasses::AUTHORITY),
584 :path => self.encode_component(uri_object.path,
585 Addressable::URI::CharacterClasses::PATH),
586 :query => self.encode_component(uri_object.query,
587 Addressable::URI::CharacterClasses::QUERY),
588 :fragment => self.encode_component(uri_object.fragment,
589 Addressable::URI::CharacterClasses::FRAGMENT)
590 )
591 if return_type == String
592 return encoded_uri.to_s
593 elsif return_type == ::Addressable::URI
594 return encoded_uri
595 end
596 end
597
598 class << self
599 alias_method :escape, :encode
600 end
601
602 ##
603 # Normalizes the encoding of a URI. Characters within a hostname are
604 # not percent encoded to allow for internationalized domain names.
605 #
606 # @param [String, Addressable::URI, #to_str] uri
607 # The URI to encode.
608 #
609 # @param [Class] return_type
610 # The type of object to return.
611 # This value may only be set to <code>String</code> or
612 # <code>Addressable::URI</code>. All other values are invalid. Defaults
613 # to <code>String</code>.
614 #
615 # @return [String, Addressable::URI]
616 # The encoded URI.
617 # The return type is determined by the <code>return_type</code>
618 # parameter.
619 def self.normalized_encode(uri, return_type=String)
620 begin
621 uri = uri.to_str
622 rescue NoMethodError, TypeError
623 raise TypeError, "Can't convert #{uri.class} into String."
624 end if !uri.is_a? String
625
626 if ![String, ::Addressable::URI].include?(return_type)
627 raise TypeError,
628 "Expected Class (String or Addressable::URI), " +
629 "got #{return_type.inspect}"
630 end
631 uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
632 components = {
633 :scheme => self.unencode_component(uri_object.scheme),
634 :user => self.unencode_component(uri_object.user),
635 :password => self.unencode_component(uri_object.password),
636 :host => self.unencode_component(uri_object.host),
637 :port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
638 :path => self.unencode_component(uri_object.path),
639 :query => self.unencode_component(uri_object.query),
640 :fragment => self.unencode_component(uri_object.fragment)
641 }
642 components.each do |key, value|
643 if value != nil
644 begin
645 components[key] =
646 Addressable::IDNA.unicode_normalize_kc(value.to_str)
647 rescue ArgumentError
648 # Likely a malformed UTF-8 character, skip unicode normalization
649 components[key] = value.to_str
650 end
651 end
652 end
653 encoded_uri = Addressable::URI.new(
654 :scheme => self.encode_component(components[:scheme],
655 Addressable::URI::CharacterClasses::SCHEME),
656 :user => self.encode_component(components[:user],
657 Addressable::URI::CharacterClasses::UNRESERVED),
658 :password => self.encode_component(components[:password],
659 Addressable::URI::CharacterClasses::UNRESERVED),
660 :host => components[:host],
661 :port => components[:port],
662 :path => self.encode_component(components[:path],
663 Addressable::URI::CharacterClasses::PATH),
664 :query => self.encode_component(components[:query],
665 Addressable::URI::CharacterClasses::QUERY),
666 :fragment => self.encode_component(components[:fragment],
667 Addressable::URI::CharacterClasses::FRAGMENT)
668 )
669 if return_type == String
670 return encoded_uri.to_s
671 elsif return_type == ::Addressable::URI
672 return encoded_uri
673 end
674 end
675
676 ##
677 # Encodes a set of key/value pairs according to the rules for the
678 # <code>application/x-www-form-urlencoded</code> MIME type.
679 #
680 # @param [#to_hash, #to_ary] form_values
681 # The form values to encode.
682 #
683 # @param [TrueClass, FalseClass] sort
684 # Sort the key/value pairs prior to encoding.
685 # Defaults to <code>false</code>.
686 #
687 # @return [String]
688 # The encoded value.
689 def self.form_encode(form_values, sort=false)
690 if form_values.respond_to?(:to_hash)
691 form_values = form_values.to_hash.to_a
692 elsif form_values.respond_to?(:to_ary)
693 form_values = form_values.to_ary
694 else
695 raise TypeError, "Can't convert #{form_values.class} into Array."
696 end
697
698 form_values = form_values.inject([]) do |accu, (key, value)|
699 if value.kind_of?(Array)
700 value.each do |v|
701 accu << [key.to_s, v.to_s]
702 end
703 else
704 accu << [key.to_s, value.to_s]
705 end
706 accu
707 end
708
709 if sort
710 # Useful for OAuth and optimizing caching systems
711 form_values = form_values.sort
712 end
713 escaped_form_values = form_values.map do |(key, value)|
714 # Line breaks are CRLF pairs
715 [
716 self.encode_component(
717 key.gsub(/(\r\n|\n|\r)/, "\r\n"),
718 CharacterClasses::UNRESERVED
719 ).gsub("%20", "+"),
720 self.encode_component(
721 value.gsub(/(\r\n|\n|\r)/, "\r\n"),
722 CharacterClasses::UNRESERVED
723 ).gsub("%20", "+")
724 ]
725 end
726 return (escaped_form_values.map do |(key, value)|
727 "#{key}=#{value}"
728 end).join("&")
729 end
730
731 ##
732 # Decodes a <code>String</code> according to the rules for the
733 # <code>application/x-www-form-urlencoded</code> MIME type.
734 #
735 # @param [String, #to_str] encoded_value
736 # The form values to decode.
737 #
738 # @return [Array]
739 # The decoded values.
740 # This is not a <code>Hash</code> because of the possibility for
741 # duplicate keys.
742 def self.form_unencode(encoded_value)
743 if !encoded_value.respond_to?(:to_str)
744 raise TypeError, "Can't convert #{encoded_value.class} into String."
745 end
746 encoded_value = encoded_value.to_str
747 split_values = encoded_value.split("&").map do |pair|
748 pair.split("=", 2)
749 end
750 return split_values.map do |(key, value)|
751 [
752 key ? self.unencode_component(
753 key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil,
754 value ? (self.unencode_component(
755 value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil
756 ]
757 end
758 end
759
760 ##
761 # Creates a new uri object from component parts.
762 #
763 # @option [String, #to_str] scheme The scheme component.
764 # @option [String, #to_str] user The user component.
765 # @option [String, #to_str] password The password component.
766 # @option [String, #to_str] userinfo
767 # The userinfo component. If this is supplied, the user and password
768 # components must be omitted.
769 # @option [String, #to_str] host The host component.
770 # @option [String, #to_str] port The port component.
771 # @option [String, #to_str] authority
772 # The authority component. If this is supplied, the user, password,
773 # userinfo, host, and port components must be omitted.
774 # @option [String, #to_str] path The path component.
775 # @option [String, #to_str] query The query component.
776 # @option [String, #to_str] fragment The fragment component.
777 #
778 # @return [Addressable::URI] The constructed URI object.
779 def initialize(options={})
780 if options.has_key?(:authority)
781 if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
782 raise ArgumentError,
783 "Cannot specify both an authority and any of the components " +
784 "within the authority."
785 end
786 end
787 if options.has_key?(:userinfo)
788 if (options.keys & [:user, :password]).any?
789 raise ArgumentError,
790 "Cannot specify both a userinfo and either the user or password."
791 end
792 end
793
794 self.defer_validation do
795 # Bunch of crazy logic required because of the composite components
796 # like userinfo and authority.
797 self.scheme = options[:scheme] if options[:scheme]
798 self.user = options[:user] if options[:user]
799 self.password = options[:password] if options[:password]
800 self.userinfo = options[:userinfo] if options[:userinfo]
801 self.host = options[:host] if options[:host]
802 self.port = options[:port] if options[:port]
803 self.authority = options[:authority] if options[:authority]
804 self.path = options[:path] if options[:path]
805 self.query = options[:query] if options[:query]
806 self.query_values = options[:query_values] if options[:query_values]
807 self.fragment = options[:fragment] if options[:fragment]
808 end
809 self.to_s
810 end
811
812 ##
813 # Freeze URI, initializing instance variables.
814 #
815 # @return [Addressable::URI] The frozen URI object.
816 def freeze
817 self.normalized_scheme
818 self.normalized_user
819 self.normalized_password
820 self.normalized_userinfo
821 self.normalized_host
822 self.normalized_port
823 self.normalized_authority
824 self.normalized_site
825 self.normalized_path
826 self.normalized_query
827 self.normalized_fragment
828 self.hash
829 super
830 end
831
832 ##
833 # The scheme component for this URI.
834 #
835 # @return [String] The scheme component.
836 def scheme
837 return instance_variable_defined?(:@scheme) ? @scheme : nil
838 end
839
840 ##
841 # The scheme component for this URI, normalized.
842 #
843 # @return [String] The scheme component, normalized.
844 def normalized_scheme
845 self.scheme && @normalized_scheme ||= (begin
846 if self.scheme =~ /^\s*ssh\+svn\s*$/i
847 "svn+ssh"
848 else
849 Addressable::URI.normalize_component(
850 self.scheme.strip.downcase,
851 Addressable::URI::CharacterClasses::SCHEME
852 )
853 end
854 end)
855 end
856
857 ##
858 # Sets the scheme component for this URI.
859 #
860 # @param [String, #to_str] new_scheme The new scheme component.
861 def scheme=(new_scheme)
862 if new_scheme && !new_scheme.respond_to?(:to_str)
863 raise TypeError, "Can't convert #{new_scheme.class} into String."
864 elsif new_scheme
865 new_scheme = new_scheme.to_str
866 end
867 if new_scheme && new_scheme !~ /[a-z][a-z0-9\.\+\-]*/i
868 raise InvalidURIError, "Invalid scheme format."
869 end
870 @scheme = new_scheme
871 @scheme = nil if @scheme.to_s.strip.empty?
872
873 # Reset dependant values
874 @normalized_scheme = nil
875 @uri_string = nil
876 @hash = nil
877
878 # Ensure we haven't created an invalid URI
879 validate()
880 end
881
882 ##
883 # The user component for this URI.
884 #
885 # @return [String] The user component.
886 def user
887 return instance_variable_defined?(:@user) ? @user : nil
888 end
889
890 ##
891 # The user component for this URI, normalized.
892 #
893 # @return [String] The user component, normalized.
894 def normalized_user
895 self.user && @normalized_user ||= (begin
896 if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
897 (!self.password || self.password.strip.empty?)
898 nil
899 else
900 Addressable::URI.normalize_component(
901 self.user.strip,
902 Addressable::URI::CharacterClasses::UNRESERVED
903 )
904 end
905 end)
906 end
907
908 ##
909 # Sets the user component for this URI.
910 #
911 # @param [String, #to_str] new_user The new user component.
912 def user=(new_user)
913 if new_user && !new_user.respond_to?(:to_str)
914 raise TypeError, "Can't convert #{new_user.class} into String."
915 end
916 @user = new_user ? new_user.to_str : nil
917
918 # You can't have a nil user with a non-nil password
919 if password != nil
920 @user = EMPTY_STR if @user.nil?
921 end
922
923 # Reset dependant values
924 @userinfo = nil
925 @normalized_userinfo = nil
926 @authority = nil
927 @normalized_user = nil
928 @uri_string = nil
929 @hash = nil
930
931 # Ensure we haven't created an invalid URI
932 validate()
933 end
934
935 ##
936 # The password component for this URI.
937 #
938 # @return [String] The password component.
939 def password
940 return instance_variable_defined?(:@password) ? @password : nil
941 end
942
943 ##
944 # The password component for this URI, normalized.
945 #
946 # @return [String] The password component, normalized.
947 def normalized_password
948 self.password && @normalized_password ||= (begin
949 if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
950 (!self.user || self.user.strip.empty?)
951 nil
952 else
953 Addressable::URI.normalize_component(
954 self.password.strip,
955 Addressable::URI::CharacterClasses::UNRESERVED
956 )
957 end
958 end)
959 end
960
961 ##
962 # Sets the password component for this URI.
963 #
964 # @param [String, #to_str] new_password The new password component.
965 def password=(new_password)
966 if new_password && !new_password.respond_to?(:to_str)
967 raise TypeError, "Can't convert #{new_password.class} into String."
968 end
969 @password = new_password ? new_password.to_str : nil
970
971 # You can't have a nil user with a non-nil password
972 @password ||= nil
973 @user ||= nil
974 if @password != nil
975 @user = EMPTY_STR if @user.nil?
976 end
977
978 # Reset dependant values
979 @userinfo = nil
980 @normalized_userinfo = nil
981 @authority = nil
982 @normalized_password = nil
983 @uri_string = nil
984 @hash = nil
985
986 # Ensure we haven't created an invalid URI
987 validate()
988 end
989
990 ##
991 # The userinfo component for this URI.
992 # Combines the user and password components.
993 #
994 # @return [String] The userinfo component.
995 def userinfo
996 current_user = self.user
997 current_password = self.password
998 (current_user || current_password) && @userinfo ||= (begin
999 if current_user && current_password
1000 "#{current_user}:#{current_password}"
1001 elsif current_user && !current_password
1002 "#{current_user}"
1003 end
1004 end)
1005 end
1006
1007 ##
1008 # The userinfo component for this URI, normalized.
1009 #
1010 # @return [String] The userinfo component, normalized.
1011 def normalized_userinfo
1012 self.userinfo && @normalized_userinfo ||= (begin
1013 current_user = self.normalized_user
1014 current_password = self.normalized_password
1015 if !current_user && !current_password
1016 nil
1017 elsif current_user && current_password
1018 "#{current_user}:#{current_password}"
1019 elsif current_user && !current_password
1020 "#{current_user}"
1021 end
1022 end)
1023 end
1024
1025 ##
1026 # Sets the userinfo component for this URI.
1027 #
1028 # @param [String, #to_str] new_userinfo The new userinfo component.
1029 def userinfo=(new_userinfo)
1030 if new_userinfo && !new_userinfo.respond_to?(:to_str)
1031 raise TypeError, "Can't convert #{new_userinfo.class} into String."
1032 end
1033 new_user, new_password = if new_userinfo
1034 [
1035 new_userinfo.to_str.strip[/^(.*):/, 1],
1036 new_userinfo.to_str.strip[/:(.*)$/, 1]
1037 ]
1038 else
1039 [nil, nil]
1040 end
1041
1042 # Password assigned first to ensure validity in case of nil
1043 self.password = new_password
1044 self.user = new_user
1045
1046 # Reset dependant values
1047 @authority = nil
1048 @uri_string = nil
1049 @hash = nil
1050
1051 # Ensure we haven't created an invalid URI
1052 validate()
1053 end
1054
1055 ##
1056 # The host component for this URI.
1057 #
1058 # @return [String] The host component.
1059 def host
1060 return instance_variable_defined?(:@host) ? @host : nil
1061 end
1062
1063 ##
1064 # The host component for this URI, normalized.
1065 #
1066 # @return [String] The host component, normalized.
1067 def normalized_host
1068 self.host && @normalized_host ||= (begin
1069 if !self.host.strip.empty?
1070 result = ::Addressable::IDNA.to_ascii(
1071 URI.unencode_component(self.host.strip.downcase)
1072 )
1073 if result =~ /[^\.]\.$/
1074 # Single trailing dots are unnecessary.
1075 result = result[0...-1]
1076 end
1077 result
1078 else
1079 EMPTY_STR
1080 end
1081 end)
1082 end
1083
1084 ##
1085 # Sets the host component for this URI.
1086 #
1087 # @param [String, #to_str] new_host The new host component.
1088 def host=(new_host)
1089 if new_host && !new_host.respond_to?(:to_str)
1090 raise TypeError, "Can't convert #{new_host.class} into String."
1091 end
1092 @host = new_host ? new_host.to_str : nil
1093
1094 unreserved = CharacterClasses::UNRESERVED
1095 sub_delims = CharacterClasses::SUB_DELIMS
1096 if @host != nil && (@host =~ /[<>{}\/\?\#\@]/ ||
1097 (@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
1098 Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
1099 raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
1100 end
1101
1102 # Reset dependant values
1103 @authority = nil
1104 @normalized_host = nil
1105 @uri_string = nil
1106 @hash = nil
1107
1108 # Ensure we haven't created an invalid URI
1109 validate()
1110 end
1111
1112 ##
1113 # This method is same as URI::Generic#host except
1114 # brackets for IPv6 (and 'IPvFuture') addresses are removed.
1115 #
1116 # @see Addressable::URI#host
1117 #
1118 # @return [String] The hostname for this URI.
1119 def hostname
1120 v = self.host
1121 /\A\[(.*)\]\z/ =~ v ? $1 : v
1122 end
1123
1124 ##
1125 # This method is same as URI::Generic#host= except
1126 # the argument can be a bare IPv6 address (or 'IPvFuture').
1127 #
1128 # @see Addressable::URI#host=
1129 #
1130 # @param [String, #to_str] new_hostname The new hostname for this URI.
1131 def hostname=(new_hostname)
1132 if new_hostname && !new_hostname.respond_to?(:to_str)
1133 raise TypeError, "Can't convert #{new_hostname.class} into String."
1134 end
1135 v = new_hostname ? new_hostname.to_str : nil
1136 v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
1137 self.host = v
1138 end
1139
1140 ##
1141 # The authority component for this URI.
1142 # Combines the user, password, host, and port components.
1143 #
1144 # @return [String] The authority component.
1145 def authority
1146 self.host && @authority ||= (begin
1147 authority = ""
1148 if self.userinfo != nil
1149 authority << "#{self.userinfo}@"
1150 end
1151 authority << self.host
1152 if self.port != nil
1153 authority << ":#{self.port}"
1154 end
1155 authority
1156 end)
1157 end
1158
1159 ##
1160 # The authority component for this URI, normalized.
1161 #
1162 # @return [String] The authority component, normalized.
1163 def normalized_authority
1164 self.authority && @normalized_authority ||= (begin
1165 authority = ""
1166 if self.normalized_userinfo != nil
1167 authority << "#{self.normalized_userinfo}@"
1168 end
1169 authority << self.normalized_host
1170 if self.normalized_port != nil
1171 authority << ":#{self.normalized_port}"
1172 end
1173 authority
1174 end)
1175 end
1176
1177 ##
1178 # Sets the authority component for this URI.
1179 #
1180 # @param [String, #to_str] new_authority The new authority component.
1181 def authority=(new_authority)
1182 if new_authority
1183 if !new_authority.respond_to?(:to_str)
1184 raise TypeError, "Can't convert #{new_authority.class} into String."
1185 end
1186 new_authority = new_authority.to_str
1187 new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
1188 if new_userinfo
1189 new_user = new_userinfo.strip[/^([^:]*):?/, 1]
1190 new_password = new_userinfo.strip[/:(.*)$/, 1]
1191 end
1192 new_host = new_authority.gsub(
1193 /^([^\[\]]*)@/, EMPTY_STR
1194 ).gsub(
1195 /:([^:@\[\]]*?)$/, EMPTY_STR
1196 )
1197 new_port =
1198 new_authority[/:([^:@\[\]]*?)$/, 1]
1199 end
1200
1201 # Password assigned first to ensure validity in case of nil
1202 self.password = defined?(new_password) ? new_password : nil
1203 self.user = defined?(new_user) ? new_user : nil
1204 self.host = defined?(new_host) ? new_host : nil
1205 self.port = defined?(new_port) ? new_port : nil
1206
1207 # Reset dependant values
1208 @userinfo = nil
1209 @normalized_userinfo = nil
1210 @uri_string = nil
1211 @hash = nil
1212
1213 # Ensure we haven't created an invalid URI
1214 validate()
1215 end
1216
1217 ##
1218 # The origin for this URI, serialized to ASCII, as per
1219 # RFC 6454, section 6.2.
1220 #
1221 # @return [String] The serialized origin.
1222 def origin
1223 return (if self.scheme && self.authority
1224 if self.normalized_port
1225 (
1226 "#{self.normalized_scheme}://#{self.normalized_host}" +
1227 ":#{self.normalized_port}"
1228 )
1229 else
1230 "#{self.normalized_scheme}://#{self.normalized_host}"
1231 end
1232 else
1233 "null"
1234 end)
1235 end
1236
1237 # Returns an array of known ip-based schemes. These schemes typically
1238 # use a similar URI form:
1239 # <code>//<user>:<password>@<host>:<port>/<url-path></code>
1240 def self.ip_based_schemes
1241 return self.port_mapping.keys
1242 end
1243
1244 # Returns a hash of common IP-based schemes and their default port
1245 # numbers. Adding new schemes to this hash, as necessary, will allow
1246 # for better URI normalization.
1247 def self.port_mapping
1248 PORT_MAPPING
1249 end
1250
1251 ##
1252 # The port component for this URI.
1253 # This is the port number actually given in the URI. This does not
1254 # infer port numbers from default values.
1255 #
1256 # @return [Integer] The port component.
1257 def port
1258 return instance_variable_defined?(:@port) ? @port : nil
1259 end
1260
1261 ##
1262 # The port component for this URI, normalized.
1263 #
1264 # @return [Integer] The port component, normalized.
1265 def normalized_port
1266 if URI.port_mapping[self.normalized_scheme] == self.port
1267 nil
1268 else
1269 self.port
1270 end
1271 end
1272
1273 ##
1274 # Sets the port component for this URI.
1275 #
1276 # @param [String, Integer, #to_s] new_port The new port component.
1277 def port=(new_port)
1278 if new_port != nil && new_port.respond_to?(:to_str)
1279 new_port = Addressable::URI.unencode_component(new_port.to_str)
1280 end
1281 if new_port != nil && !(new_port.to_s =~ /^\d+$/)
1282 raise InvalidURIError,
1283 "Invalid port number: #{new_port.inspect}"
1284 end
1285
1286 @port = new_port.to_s.to_i
1287 @port = nil if @port == 0
1288
1289 # Reset dependant values
1290 @authority = nil
1291 @normalized_port = nil
1292 @uri_string = nil
1293 @hash = nil
1294
1295 # Ensure we haven't created an invalid URI
1296 validate()
1297 end
1298
1299 ##
1300 # The inferred port component for this URI.
1301 # This method will normalize to the default port for the URI's scheme if
1302 # the port isn't explicitly specified in the URI.
1303 #
1304 # @return [Integer] The inferred port component.
1305 def inferred_port
1306 if self.port.to_i == 0
1307 self.default_port
1308 else
1309 self.port.to_i
1310 end
1311 end
1312
1313 ##
1314 # The default port for this URI's scheme.
1315 # This method will always returns the default port for the URI's scheme
1316 # regardless of the presence of an explicit port in the URI.
1317 #
1318 # @return [Integer] The default port.
1319 def default_port
1320 URI.port_mapping[self.scheme.strip.downcase] if self.scheme
1321 end
1322
1323 ##
1324 # The combination of components that represent a site.
1325 # Combines the scheme, user, password, host, and port components.
1326 # Primarily useful for HTTP and HTTPS.
1327 #
1328 # For example, <code>"http://example.com/path?query"</code> would have a
1329 # <code>site</code> value of <code>"http://example.com"</code>.
1330 #
1331 # @return [String] The components that identify a site.
1332 def site
1333 (self.scheme || self.authority) && @site ||= (begin
1334 site_string = ""
1335 site_string << "#{self.scheme}:" if self.scheme != nil
1336 site_string << "//#{self.authority}" if self.authority != nil
1337 site_string
1338 end)
1339 end
1340
1341 ##
1342 # The normalized combination of components that represent a site.
1343 # Combines the scheme, user, password, host, and port components.
1344 # Primarily useful for HTTP and HTTPS.
1345 #
1346 # For example, <code>"http://example.com/path?query"</code> would have a
1347 # <code>site</code> value of <code>"http://example.com"</code>.
1348 #
1349 # @return [String] The normalized components that identify a site.
1350 def normalized_site
1351 self.site && @normalized_site ||= (begin
1352 site_string = ""
1353 if self.normalized_scheme != nil
1354 site_string << "#{self.normalized_scheme}:"
1355 end
1356 if self.normalized_authority != nil
1357 site_string << "//#{self.normalized_authority}"
1358 end
1359 site_string
1360 end)
1361 end
1362
1363 ##
1364 # Sets the site value for this URI.
1365 #
1366 # @param [String, #to_str] new_site The new site value.
1367 def site=(new_site)
1368 if new_site
1369 if !new_site.respond_to?(:to_str)
1370 raise TypeError, "Can't convert #{new_site.class} into String."
1371 end
1372 new_site = new_site.to_str
1373 # These two regular expressions derived from the primary parsing
1374 # expression
1375 self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1]
1376 self.authority = new_site[
1377 /^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1
1378 ]
1379 else
1380 self.scheme = nil
1381 self.authority = nil
1382 end
1383 end
1384
1385 ##
1386 # The path component for this URI.
1387 #
1388 # @return [String] The path component.
1389 def path
1390 return instance_variable_defined?(:@path) ? @path : EMPTY_STR
1391 end
1392
1393 NORMPATH = /^(?!\/)[^\/:]*:.*$/
1394 ##
1395 # The path component for this URI, normalized.
1396 #
1397 # @return [String] The path component, normalized.
1398 def normalized_path
1399 @normalized_path ||= (begin
1400 path = self.path.to_s
1401 if self.scheme == nil && path =~ NORMPATH
1402 # Relative paths with colons in the first segment are ambiguous.
1403 path = path.sub(":", "%2F")
1404 end
1405 # String#split(delimeter, -1) uses the more strict splitting behavior
1406 # found by default in Python.
1407 result = (path.strip.split(SLASH, -1).map do |segment|
1408 Addressable::URI.normalize_component(
1409 segment,
1410 Addressable::URI::CharacterClasses::PCHAR
1411 )
1412 end).join(SLASH)
1413
1414 result = URI.normalize_path(result)
1415 if result.empty? &&
1416 ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
1417 result = SLASH
1418 end
1419 result
1420 end)
1421 end
1422
1423 ##
1424 # Sets the path component for this URI.
1425 #
1426 # @param [String, #to_str] new_path The new path component.
1427 def path=(new_path)
1428 if new_path && !new_path.respond_to?(:to_str)
1429 raise TypeError, "Can't convert #{new_path.class} into String."
1430 end
1431 @path = (new_path || EMPTY_STR).to_str
1432 if !@path.empty? && @path[0..0] != SLASH && host != nil
1433 @path = "/#{@path}"
1434 end
1435
1436 # Reset dependant values
1437 @normalized_path = nil
1438 @uri_string = nil
1439 @hash = nil
1440 end
1441
1442 ##
1443 # The basename, if any, of the file in the path component.
1444 #
1445 # @return [String] The path's basename.
1446 def basename
1447 # Path cannot be nil
1448 return File.basename(self.path).gsub(/;[^\/]*$/, EMPTY_STR)
1449 end
1450
1451 ##
1452 # The extname, if any, of the file in the path component.
1453 # Empty string if there is no extension.
1454 #
1455 # @return [String] The path's extname.
1456 def extname
1457 return nil unless self.path
1458 return File.extname(self.basename)
1459 end
1460
1461 ##
1462 # The query component for this URI.
1463 #
1464 # @return [String] The query component.
1465 def query
1466 return instance_variable_defined?(:@query) ? @query : nil
1467 end
1468
1469 ##
1470 # The query component for this URI, normalized.
1471 #
1472 # @return [String] The query component, normalized.
1473 def normalized_query(*flags)
1474 modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1475 # Make sure possible key-value pair delimiters are escaped.
1476 modified_query_class.sub!("\\&", "").sub!("\\;", "")
1477 pairs = (self.query || "").split("&", -1)
1478 pairs.sort! if flags.include?(:sorted)
1479 component = (pairs.map do |pair|
1480 Addressable::URI.normalize_component(pair, modified_query_class, "+")
1481 end).join("&")
1482 component == "" ? nil : component
1483 end
1484
1485 ##
1486 # Sets the query component for this URI.
1487 #
1488 # @param [String, #to_str] new_query The new query component.
1489 def query=(new_query)
1490 if new_query && !new_query.respond_to?(:to_str)
1491 raise TypeError, "Can't convert #{new_query.class} into String."
1492 end
1493 @query = new_query ? new_query.to_str : nil
1494
1495 # Reset dependant values
1496 @normalized_query = nil
1497 @uri_string = nil
1498 @hash = nil
1499 end
1500
1501 ##
1502 # Converts the query component to a Hash value.
1503 #
1504 # @param [Class] return_type The return type desired. Value must be either
1505 # `Hash` or `Array`.
1506 #
1507 # @return [Hash, Array, nil] The query string parsed as a Hash or Array
1508 # or nil if the query string is blank.
1509 #
1510 # @example
1511 # Addressable::URI.parse("?one=1&two=2&three=3").query_values
1512 # #=> {"one" => "1", "two" => "2", "three" => "3"}
1513 # Addressable::URI.parse("?one=two&one=three").query_values(Array)
1514 # #=> [["one", "two"], ["one", "three"]]
1515 # Addressable::URI.parse("?one=two&one=three").query_values(Hash)
1516 # #=> {"one" => "three"}
1517 # Addressable::URI.parse("?").query_values
1518 # #=> {}
1519 # Addressable::URI.parse("").query_values
1520 # #=> nil
1521 def query_values(return_type=Hash)
1522 empty_accumulator = Array == return_type ? [] : {}
1523 if return_type != Hash && return_type != Array
1524 raise ArgumentError, "Invalid return type. Must be Hash or Array."
1525 end
1526 return nil if self.query == nil
1527 split_query = (self.query.split("&").map do |pair|
1528 pair.split("=", 2) if pair && !pair.empty?
1529 end).compact
1530 return split_query.inject(empty_accumulator.dup) do |accu, pair|
1531 # I'd rather use key/value identifiers instead of array lookups,
1532 # but in this case I really want to maintain the exact pair structure,
1533 # so it's best to make all changes in-place.
1534 pair[0] = URI.unencode_component(pair[0])
1535 if pair[1].respond_to?(:to_str)
1536 # I loathe the fact that I have to do this. Stupid HTML 4.01.
1537 # Treating '+' as a space was just an unbelievably bad idea.
1538 # There was nothing wrong with '%20'!
1539 # If it ain't broke, don't fix it!
1540 pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
1541 end
1542 if return_type == Hash
1543 accu[pair[0]] = pair[1]
1544 else
1545 accu << pair
1546 end
1547 accu
1548 end
1549 end
1550
1551 ##
1552 # Sets the query component for this URI from a Hash object.
1553 # An empty Hash or Array will result in an empty query string.
1554 #
1555 # @param [Hash, #to_hash, Array] new_query_values The new query values.
1556 #
1557 # @example
1558 # uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
1559 # uri.query
1560 # # => "a=a&b=c&b=d&b=e"
1561 # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
1562 # uri.query
1563 # # => "a=a&b=c&b=d&b=e"
1564 # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
1565 # uri.query
1566 # # => "a=a&b=c&b=d&b=e"
1567 # uri.query_values = [['flag'], ['key', 'value']]
1568 # uri.query
1569 # # => "flag&key=value"
1570 def query_values=(new_query_values)
1571 if new_query_values == nil
1572 self.query = nil
1573 return nil
1574 end
1575
1576 if !new_query_values.is_a?(Array)
1577 if !new_query_values.respond_to?(:to_hash)
1578 raise TypeError,
1579 "Can't convert #{new_query_values.class} into Hash."
1580 end
1581 new_query_values = new_query_values.to_hash
1582 new_query_values = new_query_values.map do |key, value|
1583 key = key.to_s if key.kind_of?(Symbol)
1584 [key, value]
1585 end
1586 # Useful default for OAuth and caching.
1587 # Only to be used for non-Array inputs. Arrays should preserve order.
1588 new_query_values.sort!
1589 end
1590
1591 # new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
1592 buffer = ""
1593 new_query_values.each do |key, value|
1594 encoded_key = URI.encode_component(
1595 key, CharacterClasses::UNRESERVED
1596 )
1597 if value == nil
1598 buffer << "#{encoded_key}&"
1599 elsif value.kind_of?(Array)
1600 value.each do |sub_value|
1601 encoded_value = URI.encode_component(
1602 sub_value, CharacterClasses::UNRESERVED
1603 )
1604 buffer << "#{encoded_key}=#{encoded_value}&"
1605 end
1606 else
1607 encoded_value = URI.encode_component(
1608 value, CharacterClasses::UNRESERVED
1609 )
1610 buffer << "#{encoded_key}=#{encoded_value}&"
1611 end
1612 end
1613 self.query = buffer.chop
1614 end
1615
1616 ##
1617 # The HTTP request URI for this URI. This is the path and the
1618 # query string.
1619 #
1620 # @return [String] The request URI required for an HTTP request.
1621 def request_uri
1622 return nil if self.absolute? && self.scheme !~ /^https?$/
1623 return (
1624 (!self.path.empty? ? self.path : SLASH) +
1625 (self.query ? "?#{self.query}" : EMPTY_STR)
1626 )
1627 end
1628
1629 ##
1630 # Sets the HTTP request URI for this URI.
1631 #
1632 # @param [String, #to_str] new_request_uri The new HTTP request URI.
1633 def request_uri=(new_request_uri)
1634 if !new_request_uri.respond_to?(:to_str)
1635 raise TypeError, "Can't convert #{new_request_uri.class} into String."
1636 end
1637 if self.absolute? && self.scheme !~ /^https?$/
1638 raise InvalidURIError,
1639 "Cannot set an HTTP request URI for a non-HTTP URI."
1640 end
1641 new_request_uri = new_request_uri.to_str
1642 path_component = new_request_uri[/^([^\?]*)\?(?:.*)$/, 1]
1643 query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
1644 path_component = path_component.to_s
1645 path_component = (!path_component.empty? ? path_component : SLASH)
1646 self.path = path_component
1647 self.query = query_component
1648
1649 # Reset dependant values
1650 @uri_string = nil
1651 @hash = nil
1652 end
1653
1654 ##
1655 # The fragment component for this URI.
1656 #
1657 # @return [String] The fragment component.
1658 def fragment
1659 return instance_variable_defined?(:@fragment) ? @fragment : nil
1660 end
1661
1662 ##
1663 # The fragment component for this URI, normalized.
1664 #
1665 # @return [String] The fragment component, normalized.
1666 def normalized_fragment
1667 self.fragment && @normalized_fragment ||= (begin
1668 component = Addressable::URI.normalize_component(
1669 self.fragment,
1670 Addressable::URI::CharacterClasses::FRAGMENT
1671 )
1672 component == "" ? nil : component
1673 end)
1674 end
1675
1676 ##
1677 # Sets the fragment component for this URI.
1678 #
1679 # @param [String, #to_str] new_fragment The new fragment component.
1680 def fragment=(new_fragment)
1681 if new_fragment && !new_fragment.respond_to?(:to_str)
1682 raise TypeError, "Can't convert #{new_fragment.class} into String."
1683 end
1684 @fragment = new_fragment ? new_fragment.to_str : nil
1685
1686 # Reset dependant values
1687 @normalized_fragment = nil
1688 @uri_string = nil
1689 @hash = nil
1690
1691 # Ensure we haven't created an invalid URI
1692 validate()
1693 end
1694
1695 ##
1696 # Determines if the scheme indicates an IP-based protocol.
1697 #
1698 # @return [TrueClass, FalseClass]
1699 # <code>true</code> if the scheme indicates an IP-based protocol.
1700 # <code>false</code> otherwise.
1701 def ip_based?
1702 if self.scheme
1703 return URI.ip_based_schemes.include?(
1704 self.scheme.strip.downcase)
1705 end
1706 return false
1707 end
1708
1709 ##
1710 # Determines if the URI is relative.
1711 #
1712 # @return [TrueClass, FalseClass]
1713 # <code>true</code> if the URI is relative. <code>false</code>
1714 # otherwise.
1715 def relative?
1716 return self.scheme.nil?
1717 end
1718
1719 ##
1720 # Determines if the URI is absolute.
1721 #
1722 # @return [TrueClass, FalseClass]
1723 # <code>true</code> if the URI is absolute. <code>false</code>
1724 # otherwise.
1725 def absolute?
1726 return !relative?
1727 end
1728
1729 ##
1730 # Joins two URIs together.
1731 #
1732 # @param [String, Addressable::URI, #to_str] The URI to join with.
1733 #
1734 # @return [Addressable::URI] The joined URI.
1735 def join(uri)
1736 if !uri.respond_to?(:to_str)
1737 raise TypeError, "Can't convert #{uri.class} into String."
1738 end
1739 if !uri.kind_of?(URI)
1740 # Otherwise, convert to a String, then parse.
1741 uri = URI.parse(uri.to_str)
1742 end
1743 if uri.to_s.empty?
1744 return self.dup
1745 end
1746
1747 joined_scheme = nil
1748 joined_user = nil
1749 joined_password = nil
1750 joined_host = nil
1751 joined_port = nil
1752 joined_path = nil
1753 joined_query = nil
1754 joined_fragment = nil
1755
1756 # Section 5.2.2 of RFC 3986
1757 if uri.scheme != nil
1758 joined_scheme = uri.scheme
1759 joined_user = uri.user
1760 joined_password = uri.password
1761 joined_host = uri.host
1762 joined_port = uri.port
1763 joined_path = URI.normalize_path(uri.path)
1764 joined_query = uri.query
1765 else
1766 if uri.authority != nil
1767 joined_user = uri.user
1768 joined_password = uri.password
1769 joined_host = uri.host
1770 joined_port = uri.port
1771 joined_path = URI.normalize_path(uri.path)
1772 joined_query = uri.query
1773 else
1774 if uri.path == nil || uri.path.empty?
1775 joined_path = self.path
1776 if uri.query != nil
1777 joined_query = uri.query
1778 else
1779 joined_query = self.query
1780 end
1781 else
1782 if uri.path[0..0] == SLASH
1783 joined_path = URI.normalize_path(uri.path)
1784 else
1785 base_path = self.path.dup
1786 base_path = EMPTY_STR if base_path == nil
1787 base_path = URI.normalize_path(base_path)
1788
1789 # Section 5.2.3 of RFC 3986
1790 #
1791 # Removes the right-most path segment from the base path.
1792 if base_path =~ /\//
1793 base_path.gsub!(/\/[^\/]+$/, SLASH)
1794 else
1795 base_path = EMPTY_STR
1796 end
1797
1798 # If the base path is empty and an authority segment has been
1799 # defined, use a base path of SLASH
1800 if base_path.empty? && self.authority != nil
1801 base_path = SLASH
1802 end
1803
1804 joined_path = URI.normalize_path(base_path + uri.path)
1805 end
1806 joined_query = uri.query
1807 end
1808 joined_user = self.user
1809 joined_password = self.password
1810 joined_host = self.host
1811 joined_port = self.port
1812 end
1813 joined_scheme = self.scheme
1814 end
1815 joined_fragment = uri.fragment
1816
1817 return self.class.new(
1818 :scheme => joined_scheme,
1819 :user => joined_user,
1820 :password => joined_password,
1821 :host => joined_host,
1822 :port => joined_port,
1823 :path => joined_path,
1824 :query => joined_query,
1825 :fragment => joined_fragment
1826 )
1827 end
1828 alias_method :+, :join
1829
1830 ##
1831 # Destructive form of <code>join</code>.
1832 #
1833 # @param [String, Addressable::URI, #to_str] The URI to join with.
1834 #
1835 # @return [Addressable::URI] The joined URI.
1836 #
1837 # @see Addressable::URI#join
1838 def join!(uri)
1839 replace_self(self.join(uri))
1840 end
1841
1842 ##
1843 # Merges a URI with a <code>Hash</code> of components.
1844 # This method has different behavior from <code>join</code>. Any
1845 # components present in the <code>hash</code> parameter will override the
1846 # original components. The path component is not treated specially.
1847 #
1848 # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1849 #
1850 # @return [Addressable::URI] The merged URI.
1851 #
1852 # @see Hash#merge
1853 def merge(hash)
1854 if !hash.respond_to?(:to_hash)
1855 raise TypeError, "Can't convert #{hash.class} into Hash."
1856 end
1857 hash = hash.to_hash
1858
1859 if hash.has_key?(:authority)
1860 if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
1861 raise ArgumentError,
1862 "Cannot specify both an authority and any of the components " +
1863 "within the authority."
1864 end
1865 end
1866 if hash.has_key?(:userinfo)
1867 if (hash.keys & [:user, :password]).any?
1868 raise ArgumentError,
1869 "Cannot specify both a userinfo and either the user or password."
1870 end
1871 end
1872
1873 uri = self.class.new
1874 uri.defer_validation do
1875 # Bunch of crazy logic required because of the composite components
1876 # like userinfo and authority.
1877 uri.scheme =
1878 hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
1879 if hash.has_key?(:authority)
1880 uri.authority =
1881 hash.has_key?(:authority) ? hash[:authority] : self.authority
1882 end
1883 if hash.has_key?(:userinfo)
1884 uri.userinfo =
1885 hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
1886 end
1887 if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
1888 uri.user =
1889 hash.has_key?(:user) ? hash[:user] : self.user
1890 uri.password =
1891 hash.has_key?(:password) ? hash[:password] : self.password
1892 end
1893 if !hash.has_key?(:authority)
1894 uri.host =
1895 hash.has_key?(:host) ? hash[:host] : self.host
1896 uri.port =
1897 hash.has_key?(:port) ? hash[:port] : self.port
1898 end
1899 uri.path =
1900 hash.has_key?(:path) ? hash[:path] : self.path
1901 uri.query =
1902 hash.has_key?(:query) ? hash[:query] : self.query
1903 uri.fragment =
1904 hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
1905 end
1906
1907 return uri
1908 end
1909
1910 ##
1911 # Destructive form of <code>merge</code>.
1912 #
1913 # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1914 #
1915 # @return [Addressable::URI] The merged URI.
1916 #
1917 # @see Addressable::URI#merge
1918 def merge!(uri)
1919 replace_self(self.merge(uri))
1920 end
1921
1922 ##
1923 # Returns the shortest normalized relative form of this URI that uses the
1924 # supplied URI as a base for resolution. Returns an absolute URI if
1925 # necessary. This is effectively the opposite of <code>route_to</code>.
1926 #
1927 # @param [String, Addressable::URI, #to_str] uri The URI to route from.
1928 #
1929 # @return [Addressable::URI]
1930 # The normalized relative URI that is equivalent to the original URI.
1931 def route_from(uri)
1932 uri = URI.parse(uri).normalize
1933 normalized_self = self.normalize
1934 if normalized_self.relative?
1935 raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
1936 end
1937 if uri.relative?
1938 raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
1939 end
1940 if normalized_self == uri
1941 return Addressable::URI.parse("##{normalized_self.fragment}")
1942 end
1943 components = normalized_self.to_hash
1944 if normalized_self.scheme == uri.scheme
1945 components[:scheme] = nil
1946 if normalized_self.authority == uri.authority
1947 components[:user] = nil
1948 components[:password] = nil
1949 components[:host] = nil
1950 components[:port] = nil
1951 if normalized_self.path == uri.path
1952 components[:path] = nil
1953 if normalized_self.query == uri.query
1954 components[:query] = nil
1955 end
1956 else
1957 if uri.path != SLASH and components[:path]
1958 self_splitted_path = split_path(components[:path])
1959 uri_splitted_path = split_path(uri.path)
1960 self_dir = self_splitted_path.shift
1961 uri_dir = uri_splitted_path.shift
1962 while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
1963 self_dir = self_splitted_path.shift
1964 uri_dir = uri_splitted_path.shift
1965 end
1966 components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
1967 end
1968 end
1969 end
1970 end
1971 # Avoid network-path references.
1972 if components[:host] != nil
1973 components[:scheme] = normalized_self.scheme
1974 end
1975 return Addressable::URI.new(
1976 :scheme => components[:scheme],
1977 :user => components[:user],
1978 :password => components[:password],
1979 :host => components[:host],
1980 :port => components[:port],
1981 :path => components[:path],
1982 :query => components[:query],
1983 :fragment => components[:fragment]
1984 )
1985 end
1986
1987 ##
1988 # Returns the shortest normalized relative form of the supplied URI that
1989 # uses this URI as a base for resolution. Returns an absolute URI if
1990 # necessary. This is effectively the opposite of <code>route_from</code>.
1991 #
1992 # @param [String, Addressable::URI, #to_str] uri The URI to route to.
1993 #
1994 # @return [Addressable::URI]
1995 # The normalized relative URI that is equivalent to the supplied URI.
1996 def route_to(uri)
1997 return URI.parse(uri).route_from(self)
1998 end
1999
2000 ##
2001 # Returns a normalized URI object.
2002 #
2003 # NOTE: This method does not attempt to fully conform to specifications.
2004 # It exists largely to correct other people's failures to read the
2005 # specifications, and also to deal with caching issues since several
2006 # different URIs may represent the same resource and should not be
2007 # cached multiple times.
2008 #
2009 # @return [Addressable::URI] The normalized URI.
2010 def normalize
2011 # This is a special exception for the frequently misused feed
2012 # URI scheme.
2013 if normalized_scheme == "feed"
2014 if self.to_s =~ /^feed:\/*http:\/*/
2015 return URI.parse(
2016 self.to_s[/^feed:\/*(http:\/*.*)/, 1]
2017 ).normalize
2018 end
2019 end
2020
2021 return self.class.new(
2022 :scheme => normalized_scheme,
2023 :authority => normalized_authority,
2024 :path => normalized_path,
2025 :query => normalized_query,
2026 :fragment => normalized_fragment
2027 )
2028 end
2029
2030 ##
2031 # Destructively normalizes this URI object.
2032 #
2033 # @return [Addressable::URI] The normalized URI.
2034 #
2035 # @see Addressable::URI#normalize
2036 def normalize!
2037 replace_self(self.normalize)
2038 end
2039
2040 ##
2041 # Creates a URI suitable for display to users. If semantic attacks are
2042 # likely, the application should try to detect these and warn the user.
2043 # See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
2044 # section 7.6 for more information.
2045 #
2046 # @return [Addressable::URI] A URI suitable for display purposes.
2047 def display_uri
2048 display_uri = self.normalize
2049 display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
2050 return display_uri
2051 end
2052
2053 ##
2054 # Returns <code>true</code> if the URI objects are equal. This method
2055 # normalizes both URIs before doing the comparison, and allows comparison
2056 # against <code>Strings</code>.
2057 #
2058 # @param [Object] uri The URI to compare.
2059 #
2060 # @return [TrueClass, FalseClass]
2061 # <code>true</code> if the URIs are equivalent, <code>false</code>
2062 # otherwise.
2063 def ===(uri)
2064 if uri.respond_to?(:normalize)
2065 uri_string = uri.normalize.to_s
2066 else
2067 begin
2068 uri_string = ::Addressable::URI.parse(uri).normalize.to_s
2069 rescue InvalidURIError, TypeError
2070 return false
2071 end
2072 end
2073 return self.normalize.to_s == uri_string
2074 end
2075
2076 ##
2077 # Returns <code>true</code> if the URI objects are equal. This method
2078 # normalizes both URIs before doing the comparison.
2079 #
2080 # @param [Object] uri The URI to compare.
2081 #
2082 # @return [TrueClass, FalseClass]
2083 # <code>true</code> if the URIs are equivalent, <code>false</code>
2084 # otherwise.
2085 def ==(uri)
2086 return false unless uri.kind_of?(URI)
2087 return self.normalize.to_s == uri.normalize.to_s
2088 end
2089
2090 ##
2091 # Returns <code>true</code> if the URI objects are equal. This method
2092 # does NOT normalize either URI before doing the comparison.
2093 #
2094 # @param [Object] uri The URI to compare.
2095 #
2096 # @return [TrueClass, FalseClass]
2097 # <code>true</code> if the URIs are equivalent, <code>false</code>
2098 # otherwise.
2099 def eql?(uri)
2100 return false unless uri.kind_of?(URI)
2101 return self.to_s == uri.to_s
2102 end
2103
2104 ##
2105 # A hash value that will make a URI equivalent to its normalized
2106 # form.
2107 #
2108 # @return [Integer] A hash of the URI.
2109 def hash
2110 return @hash ||= (self.to_s.hash * -1)
2111 end
2112
2113 ##
2114 # Clones the URI object.
2115 #
2116 # @return [Addressable::URI] The cloned URI.
2117 def dup
2118 duplicated_uri = self.class.new(
2119 :scheme => self.scheme ? self.scheme.dup : nil,
2120 :user => self.user ? self.user.dup : nil,
2121 :password => self.password ? self.password.dup : nil,
2122 :host => self.host ? self.host.dup : nil,
2123 :port => self.port,
2124 :path => self.path ? self.path.dup : nil,
2125 :query => self.query ? self.query.dup : nil,
2126 :fragment => self.fragment ? self.fragment.dup : nil
2127 )
2128 return duplicated_uri
2129 end
2130
2131 ##
2132 # Omits components from a URI.
2133 #
2134 # @param [Symbol] *components The components to be omitted.
2135 #
2136 # @return [Addressable::URI] The URI with components omitted.
2137 #
2138 # @example
2139 # uri = Addressable::URI.parse("http://example.com/path?query")
2140 # #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
2141 # uri.omit(:scheme, :authority)
2142 # #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
2143 def omit(*components)
2144 invalid_components = components - [
2145 :scheme, :user, :password, :userinfo, :host, :port, :authority,
2146 :path, :query, :fragment
2147 ]
2148 unless invalid_components.empty?
2149 raise ArgumentError,
2150 "Invalid component names: #{invalid_components.inspect}."
2151 end
2152 duplicated_uri = self.dup
2153 duplicated_uri.defer_validation do
2154 components.each do |component|
2155 duplicated_uri.send((component.to_s + "=").to_sym, nil)
2156 end
2157 duplicated_uri.user = duplicated_uri.normalized_user
2158 end
2159 duplicated_uri
2160 end
2161
2162 ##
2163 # Destructive form of omit.
2164 #
2165 # @param [Symbol] *components The components to be omitted.
2166 #
2167 # @return [Addressable::URI] The URI with components omitted.
2168 #
2169 # @see Addressable::URI#omit
2170 def omit!(*components)
2171 replace_self(self.omit(*components))
2172 end
2173
2174 ##
2175 # Determines if the URI is an empty string.
2176 #
2177 # @return [TrueClass, FalseClass]
2178 # Returns <code>true</code> if empty, <code>false</code> otherwise.
2179 def empty?
2180 return self.to_s.empty?
2181 end
2182
2183 ##
2184 # Converts the URI to a <code>String</code>.
2185 #
2186 # @return [String] The URI's <code>String</code> representation.
2187 def to_s
2188 if self.scheme == nil && self.path != nil && !self.path.empty? &&
2189 self.path =~ NORMPATH
2190 raise InvalidURIError,
2191 "Cannot assemble URI string with ambiguous path: '#{self.path}'"
2192 end
2193 @uri_string ||= (begin
2194 uri_string = ""
2195 uri_string << "#{self.scheme}:" if self.scheme != nil
2196 uri_string << "//#{self.authority}" if self.authority != nil
2197 uri_string << self.path.to_s
2198 uri_string << "?#{self.query}" if self.query != nil
2199 uri_string << "##{self.fragment}" if self.fragment != nil
2200 if uri_string.respond_to?(:force_encoding)
2201 uri_string.force_encoding(Encoding::UTF_8)
2202 end
2203 uri_string
2204 end)
2205 end
2206
2207 ##
2208 # URI's are glorified <code>Strings</code>. Allow implicit conversion.
2209 alias_method :to_str, :to_s
2210
2211 ##
2212 # Returns a Hash of the URI components.
2213 #
2214 # @return [Hash] The URI as a <code>Hash</code> of components.
2215 def to_hash
2216 return {
2217 :scheme => self.scheme,
2218 :user => self.user,
2219 :password => self.password,
2220 :host => self.host,
2221 :port => self.port,
2222 :path => self.path,
2223 :query => self.query,
2224 :fragment => self.fragment
2225 }
2226 end
2227
2228 ##
2229 # Returns a <code>String</code> representation of the URI object's state.
2230 #
2231 # @return [String] The URI object's state, as a <code>String</code>.
2232 def inspect
2233 sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
2234 end
2235
2236 ##
2237 # This method allows you to make several changes to a URI simultaneously,
2238 # which separately would cause validation errors, but in conjunction,
2239 # are valid. The URI will be revalidated as soon as the entire block has
2240 # been executed.
2241 #
2242 # @param [Proc] block
2243 # A set of operations to perform on a given URI.
2244 def defer_validation(&block)
2245 raise LocalJumpError, "No block given." unless block
2246 @validation_deferred = true
2247 block.call()
2248 @validation_deferred = false
2249 validate
2250 return nil
2251 end
2252
2253 private
2254 SELF_REF = '.'
2255 PARENT = '..'
2256
2257 RULE_2A = /\/\.\/|\/\.$/
2258 RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
2259 RULE_2D = /^\.\.?\/?/
2260 RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
2261
2262 ##
2263 # Resolves paths to their simplest form.
2264 #
2265 # @param [String] path The path to normalize.
2266 #
2267 # @return [String] The normalized path.
2268 def self.normalize_path(path)
2269 # Section 5.2.4 of RFC 3986
2270
2271 return nil if path.nil?
2272 normalized_path = path.dup
2273 begin
2274 mod = nil
2275 mod ||= normalized_path.gsub!(RULE_2A, SLASH)
2276
2277 pair = normalized_path.match(RULE_2B_2C)
2278 parent, current = pair[1], pair[2] if pair
2279 if pair && ((parent != SELF_REF && parent != PARENT) ||
2280 (current != SELF_REF && current != PARENT))
2281 mod ||= normalized_path.gsub!(
2282 Regexp.new(
2283 "/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
2284 "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
2285 ), SLASH
2286 )
2287 end
2288
2289 mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
2290 # Non-standard, removes prefixed dotted segments from path.
2291 mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
2292 end until mod.nil?
2293
2294 return normalized_path
2295 end
2296
2297 ##
2298 # Ensures that the URI is valid.
2299 def validate
2300 return if !!@validation_deferred
2301 if self.scheme != nil && self.ip_based? &&
2302 (self.host == nil || self.host.empty?) &&
2303 (self.path == nil || self.path.empty?)
2304 raise InvalidURIError,
2305 "Absolute URI missing hierarchical segment: '#{self.to_s}'"
2306 end
2307 if self.host == nil
2308 if self.port != nil ||
2309 self.user != nil ||
2310 self.password != nil
2311 raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
2312 end
2313 end
2314 if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
2315 self.authority != nil
2316 raise InvalidURIError,
2317 "Cannot have a relative path with an authority set: '#{self.to_s}'"
2318 end
2319 return nil
2320 end
2321
2322 ##
2323 # Replaces the internal state of self with the specified URI's state.
2324 # Used in destructive operations to avoid massive code repetition.
2325 #
2326 # @param [Addressable::URI] uri The URI to replace <code>self</code> with.
2327 #
2328 # @return [Addressable::URI] <code>self</code>.
2329 def replace_self(uri)
2330 # Reset dependant values
2331 instance_variables.each do |var|
2332 instance_variable_set(var, nil)
2333 end
2334
2335 @scheme = uri.scheme
2336 @user = uri.user
2337 @password = uri.password
2338 @host = uri.host
2339 @port = uri.port
2340 @path = uri.path
2341 @query = uri.query
2342 @fragment = uri.fragment
2343 return self
2344 end
2345
2346 ##
2347 # Splits path string with "/"(slash).
2348 # It is considered that there is empty string after last slash when
2349 # path ends with slash.
2350 #
2351 # @param [String] path The path to split.
2352 #
2353 # @return [Array<String>] An array of parts of path.
2354 def split_path(path)
2355 splitted = path.split(SLASH)
2356 splitted << EMPTY_STR if path.end_with? SLASH
2357 splitted
2358 end
2359 end
2360 end
+0
-30
vendor/addressable/version.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2015 Bob Aman
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #++
16
17
18 # Used to prevent the class/module from being loaded more than once
19 if !defined?(Addressable::VERSION)
20 module Addressable
21 module VERSION
22 MAJOR = 2
23 MINOR = 3
24 TINY = 8
25
26 STRING = [MAJOR, MINOR, TINY].join('.')
27 end
28 end
29 end
+0
-20
vendor/delayer/error.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer
3 class Error < ::StandardError; end
4 class TooLate < Error; end
5 class AlreadyExecutedError < TooLate; end
6 class AlreadyCanceledError < TooLate; end
7 class AlreadyRunningError < TooLate; end
8 class InvalidPriorityError < Error; end
9 def self.StateError(state)
10 case state
11 when :run
12 AlreadyRunningError
13 when :done
14 AlreadyExecutedError
15 when :cancel
16 AlreadyCanceledError
17 end
18 end
19 end
+0
-131
vendor/delayer/extend.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer
3 module Extend
4 attr_accessor :expire
5 attr_reader :exception
6
7 def self.extended(klass)
8 klass.class_eval do
9 @first_pointer = @last_pointer = nil
10 @busy = false
11 @expire = 0
12 @remain_hook = nil
13 @exception = nil
14 @remain_received = false
15 @lock = Mutex.new
16 end
17 end
18
19 # Run registered jobs.
20 # ==== Args
21 # [current_expire] expire for processing (secs, 0=unexpired)
22 # ==== Return
23 # self
24 def run(current_expire = @expire)
25 if 0 == current_expire
26 run_once while not empty?
27 else
28 @end_time = Time.new.to_f + @expire
29 run_once while not(empty?) and @end_time >= Time.new.to_f
30 @end_time = nil
31 end
32 if @remain_hook
33 @remain_received = !empty?
34 @remain_hook.call if @remain_received
35 end
36 rescue Exception => e
37 @exception = e
38 raise e
39 end
40
41 def expire?
42 if defined?(@end_time) and @end_time
43 @end_time < Time.new.to_f
44 else
45 false
46 end
47 end
48
49 # Run a job and forward pointer.
50 # ==== Return
51 # self
52 def run_once
53 if @first_pointer
54 @busy = true
55 procedure = forward
56 procedure = forward while @first_pointer and procedure.canceled?
57 procedure.run unless procedure.canceled?
58 end
59 ensure
60 @busy = false
61 end
62
63 # Return if some jobs processing now.
64 # ==== Args
65 # [args]
66 # ==== Return
67 # true if Delayer processing job
68 def busy?
69 @busy
70 end
71
72 # Return true if no jobs has.
73 # ==== Return
74 # true if no jobs has.
75 def empty?
76 !@first_pointer
77 end
78
79 # Return remain jobs quantity.
80 # ==== Return
81 # Count of remain jobs
82 def size(node = @first_pointer)
83 if node
84 1 + size(node.next)
85 else
86 0
87 end
88 end
89
90 # register new job.
91 # ==== Args
92 # [procedure] job(Delayer::Procedure)
93 # ==== Return
94 # self
95 def register(procedure)
96 lock.synchronize do
97 if @last_pointer
98 @last_pointer = @last_pointer.break procedure
99 else
100 @last_pointer = @first_pointer = procedure
101 end
102 if @remain_hook and not @remain_received
103 @remain_received = true
104 @remain_hook.call
105 end
106 end
107 self
108 end
109
110 def register_remain_hook
111 @remain_hook = Proc.new
112 end
113
114 private
115
116 def forward
117 lock.synchronize do
118 prev = @first_pointer
119 @first_pointer = @first_pointer.next
120 @last_pointer = nil unless @first_pointer
121 prev
122 end
123 end
124
125 def lock
126 @lock
127 end
128
129 end
130 end
+0
-84
vendor/delayer/priority.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer
3 module Priority
4 attr_reader :priority
5
6 def self.included(klass)
7 klass.class_eval do
8 include ::Delayer
9 extend Extend
10 end
11 end
12
13 def initialize(priority = self.class.instance_eval{ @default_priority }, *args)
14 self.class.validate_priority priority
15 @priority = priority
16 super(*args)
17 end
18
19 module Extend
20 def self.extended(klass)
21 klass.class_eval do
22 @priority_pointer = {}
23 end
24 end
25
26 # register new job.
27 # ==== Args
28 # [procedure] job(Delayer::Procedure)
29 # ==== Return
30 # self
31 def register(procedure)
32 priority = procedure.delayer.priority
33 lock.synchronize do
34 last_pointer = get_prev_point(priority)
35 if last_pointer
36 @priority_pointer[priority] = last_pointer.break procedure
37 else
38 procedure.next = @first_pointer
39 @priority_pointer[priority] = @first_pointer = procedure
40 end
41 if @last_pointer
42 @last_pointer = @priority_pointer[priority]
43 end
44 if @remain_hook and not @remain_received
45 @remain_received = true
46 @remain_hook.call
47 end
48 end
49 self
50 end
51
52 def get_prev_point(priority)
53 if @priority_pointer[priority]
54 @priority_pointer[priority]
55 else
56 next_index = @priorities.index(priority) - 1
57 get_prev_point @priorities[next_index] if 0 <= next_index
58 end
59 end
60
61 def validate_priority(symbol)
62 unless @priorities.include? symbol
63 raise Delayer::InvalidPriorityError, "undefined priority '#{symbol}'"
64 end
65 end
66
67 private
68
69 def forward
70 lock.synchronize do
71 prev = @first_pointer
72 @first_pointer = @first_pointer.next
73 @last_pointer = nil unless @first_pointer
74 @priority_pointer.each do |priority, pointer|
75 @priority_pointer[priority] = @first_pointer if prev == pointer
76 end
77 prev
78 end
79 end
80
81 end
82 end
83 end
+0
-61
vendor/delayer/procedure.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer
3 class Procedure
4 attr_reader :state, :delayer
5 attr_accessor :next
6 def initialize(delayer, &proc)
7 @delayer, @proc = delayer, proc
8 @state = :stop
9 @next = nil
10 @delayer.class.register(self)
11 end
12
13 # Run a process
14 # ==== Exception
15 # Delayer::TooLate :: if already called run()
16 # ==== Return
17 # node
18 def run
19 unless :stop == @state
20 raise Delayer::StateError(@state), "call twice Delayer::Procedure"
21 end
22 @state = :run
23 @proc.call
24 @state = :done
25 @proc = nil
26 end
27
28 # Cancel this job
29 # ==== Exception
30 # Delayer::TooLate :: if already called run()
31 # ==== Return
32 # self
33 def cancel
34 unless :stop == @state
35 raise Delayer::StateError(@state), "cannot cancel Delayer::Procedure"
36 end
37 @state = :cancel
38 self
39 end
40
41 # Return true if canceled this task
42 # ==== Return
43 # true if canceled this task
44 def canceled?
45 :cancel == @state
46 end
47
48 # insert node between self and self.next
49 # ==== Args
50 # [node] insertion
51 # ==== Return
52 # node
53 def break(node)
54 tail = @next
55 @next = node
56 node.next = tail
57 node
58 end
59 end
60 end
+0
-3
vendor/delayer/version.rb less more
0 module Delayer
1 VERSION = "0.0.2"
2 end
+0
-61
vendor/delayer.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/version"
2 require "delayer/error"
3 require "delayer/extend"
4 require "delayer/procedure"
5 require "delayer/priority"
6 require "monitor"
7
8 module Delayer
9 class << self
10 attr_accessor :default
11
12 def included(klass)
13 klass.extend Extend
14 end
15
16 # Generate new Delayer class.
17 # ==== Args
18 # [options]
19 # Hash
20 # expire :: processing expire (secs, 0=unlimited)
21 # priority :: priorities
22 # default :: default priotity
23 # ==== Return
24 # A new class
25 def generate_class(options = {})
26 if options[:priority]
27 Class.new do
28 include Priority
29 @expire = options[:expire] || 0
30 @priorities = options[:priority]
31 @default_priority = options[:default]
32 end
33 else
34 Class.new do
35 include ::Delayer
36 @expire = options[:expire] || 0
37 end
38 end
39 end
40
41 def method_missing(*args, &proc)
42 (@default ||= generate_class).__send__(*args, &proc)
43 end
44 end
45
46 def initialize(*args)
47 super
48 @procedure = Procedure.new(self, &Proc.new)
49 end
50
51 # Cancel this job
52 # ==== Exception
53 # Delayer::AlreadyExecutedError :: if already called run()
54 # ==== Return
55 # self
56 def cancel
57 @procedure.cancel
58 self
59 end
60 end
+0
-104
vendor/digest/hmac.rb less more
0 # = digest/hmac.rb
1 #
2 # An implementation of HMAC keyed-hashing algorithm
3 #
4 # == Overview
5 #
6 # This library adds a method named hmac() to Digest classes, which
7 # creates a Digest class for calculating HMAC digests.
8 #
9 # == Examples
10 #
11 # require 'digest/hmac'
12 #
13 # # one-liner example
14 # puts Digest::HMAC.hexdigest("data", "hash key", Digest::SHA1)
15 #
16 # # rather longer one
17 # hmac = Digest::HMAC.new("foo", Digest::RMD160)
18 #
19 # buf = ""
20 # while stream.read(16384, buf)
21 # hmac.update(buf)
22 # end
23 #
24 # puts hmac.bubblebabble
25 #
26 # == License
27 #
28 # Copyright (c) 2006 Akinori MUSHA <knu@iDaemons.org>
29 #
30 # Documentation by Akinori MUSHA
31 #
32 # All rights reserved. You can redistribute and/or modify it under
33 # the same terms as Ruby.
34 #
35 # $Id: hmac.rb 14881 2008-01-04 07:26:14Z akr $
36 #
37
38 require 'digest'
39
40 unless defined?(Digest::HMAC)
41 module Digest
42 class HMAC < Digest::Class
43 def initialize(key, digester)
44 @md = digester.new
45
46 block_len = @md.block_length
47
48 if key.bytesize > block_len
49 key = @md.digest(key)
50 end
51
52 ipad = Array.new(block_len).fill(0x36)
53 opad = Array.new(block_len).fill(0x5c)
54
55 key.bytes.each_with_index { |c, i|
56 ipad[i] ^= c
57 opad[i] ^= c
58 }
59
60 @key = key.freeze
61 @ipad = ipad.inject('') { |s, c| s << c.chr }.freeze
62 @opad = opad.inject('') { |s, c| s << c.chr }.freeze
63 @md.update(@ipad)
64 end
65
66 def initialize_copy(other)
67 @md = other.instance_eval { @md.clone }
68 end
69
70 def update(text)
71 @md.update(text)
72 self
73 end
74 alias << update
75
76 def reset
77 @md.reset
78 @md.update(@ipad)
79 self
80 end
81
82 def finish
83 d = @md.digest!
84 @md.update(@opad)
85 @md.update(d)
86 @md.digest!
87 end
88 private :finish
89
90 def digest_length
91 @md.digest_length
92 end
93
94 def block_length
95 @md.block_length
96 end
97
98 def inspect
99 sprintf('#<%s: key=%s, digest=%s>', self.class.name, @key.inspect, @md.inspect.sub(/^\#<(.*)>$/) { $1 });
100 end
101 end
102 end
103 end
+0
-39
vendor/gettext/cgi.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 gettext/cgi.rb - GetText for CGI
4
5 Copyright (C) 2005-2009 Masao Mutoh
6
7 You may redistribute it and/or modify it under the same
8 license terms as Ruby or LGPL.
9 =end
10
11 require 'cgi'
12 require 'gettext'
13
14 Locale.init(:driver => :cgi)
15
16 module GetText
17
18 # Sets a CGI object. This methods is appeared when requiring "gettext/cgi".
19 # * cgi_: CGI object
20 # * Returns: self
21 def set_cgi(cgi_)
22 Locale.set_cgi(cgi_)
23 end
24
25 # Same as GetText.set_cgi. This methods is appeared when requiring "gettext/cgi".
26 # * cgi_: CGI object
27 # * Returns: cgi_
28 def cgi=(cgi_)
29 set_cgi(cgi_)
30 cgi_
31 end
32
33 # Gets the CGI object. If it is nil, returns new CGI object. This methods is appeared when requiring "gettext/cgi".
34 # * Returns: the CGI object
35 def cgi
36 Locale.cgi
37 end
38 end
+0
-64
vendor/gettext/class_info.rb less more
0 # -*- coding: utf-8 -*-
1
2 module GetText
3 # For normalize/finding the related classes/modules.
4 # This is used for realizing the scope of TextDomain.
5 # (see: http://www.yotabanana.com/hiki/ruby-gettext-scope.html)
6 module ClassInfo
7 extend self
8 # normalize the class name
9 # klass should kind of the class, not object.
10 def normalize_class(klass)
11 ret = (klass.kind_of? Module) ? klass : klass.class
12 if ret.name =~ /^\#<|^$/ or ret == GetText or ret.name.nil?
13 ret = Object
14 end
15 ret
16 end
17
18 def root_ancestors # :nodoc:
19 Object.ancestors
20 end
21
22 # Internal method for related_classes.
23 def related_classes_internal(klass, all_classes = [], analyzed_classes = [] )
24 ret = []
25 klass = normalize_class(klass)
26
27 return [Object] if root_ancestors.include? klass
28
29 ary = klass.name.split(/::/)
30 while(v = ary.shift)
31 ret.unshift(((ret.size == 0) ? Object.const_get(v) : ret[0].const_get(v)))
32 end
33 ret -= analyzed_classes
34 if ret.size > 1
35 ret += related_classes_internal(ret[1], all_classes, analyzed_classes)
36 ret.uniq!
37 end
38 analyzed_classes << klass unless analyzed_classes.include? klass
39
40 klass.ancestors.each do |a|
41 next if a == klass
42 ret += related_classes_internal(a, all_classes, analyzed_classes)
43 ret.uniq!
44 end
45
46 if all_classes.size > 0
47 (ret & all_classes).uniq
48 else
49 ret.uniq
50 end
51 end
52
53 # Returns the classes which related to klass
54 # (klass's ancestors, included modules and nested modules)
55 def related_classes(klass, all_classes = [])
56 ret = related_classes_internal(klass, all_classes)
57 unless ret.include? Object
58 ret += [Object]
59 end
60 ret
61 end
62 end
63 end
+0
-113
vendor/gettext/locale_path.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 locale_path.rb - GetText::LocalePath
4
5 Copyright (C) 2001-2010 Masao Mutoh
6
7 You may redistribute it and/or modify it under the same
8 license terms as Ruby or LGPL.
9
10 =end
11
12 require 'rbconfig'
13
14 module GetText
15 # Treats locale-path for mo-files.
16 class LocalePath
17 # The default locale paths.
18 CONFIG_PREFIX = RbConfig::CONFIG['prefix'].gsub(/\/local/, "")
19 DEFAULT_RULES = [
20 "./locale/%{lang}/LC_MESSAGES/%{name}.mo",
21 "./locale/%{lang}/%{name}.mo",
22 "#{RbConfig::CONFIG['datadir']}/locale/%{lang}/LC_MESSAGES/%{name}.mo",
23 "#{RbConfig::CONFIG['datadir'].gsub(/\/local/, "")}/locale/%{lang}/LC_MESSAGES/%{name}.mo",
24 "#{CONFIG_PREFIX}/share/locale/%{lang}/LC_MESSAGES/%{name}.mo",
25 "#{CONFIG_PREFIX}/local/share/locale/%{lang}/LC_MESSAGES/%{name}.mo"
26 ].uniq
27
28 class << self
29 # Add default locale path. Usually you should use GetText.add_default_locale_path instead.
30 # * path: a new locale path. (e.g.) "/usr/share/locale/%{lang}/LC_MESSAGES/%{name}.mo"
31 # ('locale' => "ja_JP", 'name' => "textdomain")
32 # * Returns: the new DEFAULT_LOCALE_PATHS
33 def add_default_rule(path)
34 DEFAULT_RULES.unshift(path)
35 end
36
37 # Returns path rules as an Array.
38 # (e.g.) ["/usr/share/locale/%{lang}/LC_MESSAGES/%{name}.mo", ...]
39 def default_path_rules
40 default_path_rules = []
41
42 if ENV["GETTEXT_PATH"]
43 ENV["GETTEXT_PATH"].split(/,/).each {|i|
44 default_path_rules += ["#{i}/%{lang}/LC_MESSAGES/%{name}.mo", "#{i}/%{lang}/%{name}.mo"]
45 }
46 end
47 default_path_rules += DEFAULT_RULES
48
49 load_path = $LOAD_PATH.dup
50 load_path.map!{|v| v.match(/(.*?)(\/lib)*?$/); $1}
51 load_path.each {|path|
52 default_path_rules += [
53 "#{path}/data/locale/%{lang}/LC_MESSAGES/%{name}.mo",
54 "#{path}/data/locale/%{lang}/%{name}.mo",
55 "#{path}/locale/%{lang}/LC_MESSAGES/%{name}.mo",
56 "#{path}/locale/%{lang}/%{name}.mo",
57 ]
58 }
59 # paths existed only.
60 default_path_rules = default_path_rules.select{|path|
61 Dir.glob(path % {:lang => "*", :name => "*"}).size > 0}.uniq
62 default_path_rules
63 end
64 end
65
66 attr_reader :locale_paths, :supported_locales
67
68 # Creates a new GetText::TextDomain.
69 # * name: the textdomain name.
70 # * topdir: the locale path ("%{topdir}/%{lang}/LC_MESSAGES/%{name}.mo") or nil.
71 def initialize(name, topdir = nil)
72 @name = name
73
74 if topdir
75 path_rules = ["#{topdir}/%{lang}/LC_MESSAGES/%{name}.mo", "#{topdir}/%{lang}/%{name}.mo"]
76 else
77 path_rules = self.class.default_path_rules
78 end
79
80 @locale_paths = {}
81 path_rules.each do |rule|
82 this_path_rules = rule % {:lang => "([^\/]+)", :name => name}
83 Dir.glob(rule % {:lang => "*", :name => name}).each do |path|
84 if /#{this_path_rules}/ =~ path
85 @locale_paths[$1] = path.untaint unless @locale_paths[$1]
86 end
87 end
88 end
89 @supported_locales = @locale_paths.keys.sort
90 end
91
92 # Gets the current path.
93 # * lang: a Locale::Tag.
94 def current_path(lang)
95 lang_candidates = lang.to_posix.candidates
96
97 lang_candidates.each do |tag|
98 path = @locale_paths[tag.to_s]
99 warn "GetText::TextDomain#load_mo: mo-file is #{path}" if $DEBUG
100 return path if path
101 end
102
103 if $DEBUG
104 warn "MO file is not found in"
105 @locale_paths.each do |path|
106 warn " #{path[1]}"
107 end
108 end
109 nil
110 end
111 end
112 end
+0
-367
vendor/gettext/mo.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 mo.rb - A simple class for operating GNU MO file.
4
5 Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
6 Copyright (C) 2003-2009 Masao Mutoh
7 Copyright (C) 2002 Masahiro Sakai, Masao Mutoh
8 Copyright (C) 2001 Masahiro Sakai
9
10 Masahiro Sakai <s01397ms at sfc.keio.ac.jp>
11 Masao Mutoh <mutomasa at gmail.com>
12
13 You can redistribute this file and/or modify it under the same term
14 of Ruby. License of Ruby is included with Ruby distribution in
15 the file "README".
16
17 =end
18
19 require 'stringio'
20
21 module GetText
22 class MO < Hash
23 class InvalidFormat < RuntimeError; end;
24
25 attr_reader :filename
26
27 Header = Struct.new(:magic,
28 :revision,
29 :nstrings,
30 :orig_table_offset,
31 :translated_table_offset,
32 :hash_table_size,
33 :hash_table_offset)
34
35 # The following are only used in .mo files
36 # with minor revision >= 1.
37 class HeaderRev1 < Header
38 attr_accessor :n_sysdep_segments,
39 :sysdep_segments_offset,
40 :n_sysdep_strings,
41 :orig_sysdep_tab_offset,
42 :trans_sysdep_tab_offset
43 end
44
45 MAGIC_BIG_ENDIAN = "\x95\x04\x12\xde".force_encoding("ASCII-8BIT")
46 MAGIC_LITTLE_ENDIAN = "\xde\x12\x04\x95".force_encoding("ASCII-8BIT")
47
48 def self.open(arg = nil, output_charset = nil)
49 result = self.new(output_charset)
50 result.load(arg)
51 end
52
53 def initialize(output_charset = nil)
54 @filename = nil
55 @last_modified = nil
56 @little_endian = true
57 @output_charset = output_charset
58 @plural_proc = nil
59 super()
60 end
61
62 def store(msgid, msgstr, options)
63 string = generate_original_string(msgid, options)
64 self[string] = msgstr
65 end
66
67 def update!
68 if FileTest.exist?(@filename)
69 st = File.stat(@filename)
70 load(@filename) unless (@last_modified == [st.ctime, st.mtime])
71 else
72 warn "#{@filename} was lost." if $DEBUG
73 clear
74 end
75 self
76 end
77
78 def load(arg)
79 if arg.kind_of? String
80 begin
81 st = File.stat(arg)
82 @last_modified = [st.ctime, st.mtime]
83 rescue Exception
84 end
85 load_from_file(arg)
86 else
87 load_from_stream(arg)
88 end
89 @filename = arg
90 self
91 end
92
93 def load_from_stream(io)
94 magic = io.read(4)
95 case magic
96 when MAGIC_BIG_ENDIAN
97 @little_endian = false
98 when MAGIC_LITTLE_ENDIAN
99 @little_endian = true
100 else
101 raise InvalidFormat.new(sprintf("Unknown signature %s", magic.dump))
102 end
103
104 endian_type6 = @little_endian ? 'V6' : 'N6'
105 endian_type_astr = @little_endian ? 'V*' : 'N*'
106
107 header = HeaderRev1.new(magic, *(io.read(4 * 6).unpack(endian_type6)))
108
109 if header.revision == 1
110 # FIXME: It doesn't support sysdep correctly.
111 header.n_sysdep_segments = io.read(4).unpack(endian_type6)
112 header.sysdep_segments_offset = io.read(4).unpack(endian_type6)
113 header.n_sysdep_strings = io.read(4).unpack(endian_type6)
114 header.orig_sysdep_tab_offset = io.read(4).unpack(endian_type6)
115 header.trans_sysdep_tab_offset = io.read(4).unpack(endian_type6)
116 elsif header.revision > 1
117 raise InvalidFormat.new(sprintf("file format revision %d isn't supported", header.revision))
118 end
119 io.pos = header.orig_table_offset
120 orig_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
121
122 io.pos = header.translated_table_offset
123 trans_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
124
125 msgids = Array.new(header.nstrings)
126 for i in 0...header.nstrings
127 io.pos = orig_table_data[i * 2 + 1]
128 msgids[i] = io.read(orig_table_data[i * 2 + 0])
129 end
130
131 clear
132 for i in 0...header.nstrings
133 io.pos = trans_table_data[i * 2 + 1]
134 msgstr = io.read(trans_table_data[i * 2 + 0])
135
136 msgid = msgids[i]
137 if msgid.nil? || msgid.empty?
138 if msgstr
139 @charset = nil
140 @nplurals = nil
141 @plural = nil
142 msgstr.each_line{|line|
143 if /^Content-Type:/i =~ line and /charset=((?:\w|-)+)/i =~ line
144 @charset = $1
145 elsif /^Plural-Forms:\s*nplurals\s*\=\s*(\d*);\s*plural\s*\=\s*([^;]*)\n?/ =~ line
146 @nplurals = $1
147 @plural = $2
148 end
149 break if @charset and @nplurals
150 }
151 @nplurals = "1" unless @nplurals
152 @plural = "0" unless @plural
153 end
154 else
155 unless msgstr.nil?
156 msgstr = convert_encoding(msgstr, msgid)
157 end
158 end
159 self[convert_encoding(msgid, msgid)] = msgstr.freeze
160 end
161 self
162 end
163
164 def prime?(number)
165 ('1' * number) !~ /^1?$|^(11+?)\1+$/
166 end
167
168 begin
169 require 'prime'
170 def next_prime(seed)
171 Prime.instance.find{|x| x > seed }
172 end
173 rescue LoadError
174 def next_prime(seed)
175 require 'mathn'
176 prime = Prime.new
177 while current = prime.succ
178 return current if current > seed
179 end
180 end
181 end
182
183 HASHWORDBITS = 32
184 # From gettext-0.12.1/gettext-runtime/intl/hash-string.h
185 # Defines the so called `hashpjw' function by P.J. Weinberger
186 # [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
187 # 1986, 1987 Bell Telephone Laboratories, Inc.]
188 def hash_string(str)
189 hval = 0
190 str.each_byte do |b|
191 break if b == '\0'
192 hval <<= 4
193 hval += b.to_i
194 g = hval & (0xf << (HASHWORDBITS - 4))
195 if (g != 0)
196 hval ^= g >> (HASHWORDBITS - 8)
197 hval ^= g
198 end
199 end
200 hval
201 end
202
203 #Save data as little endian format.
204 def save_to_stream(io)
205 # remove untranslated message
206 translated_messages = reject do |msgid, msgstr|
207 msgstr.nil?
208 end
209
210 size = translated_messages.size
211 header_size = 4 * 7
212 table_size = 4 * 2 * size
213
214 hash_table_size = next_prime((size * 4) / 3)
215 hash_table_size = 3 if hash_table_size <= 2
216 header = Header.new(
217 MAGIC_LITTLE_ENDIAN, # magic
218 0, # revision
219 size, # nstrings
220 header_size, # orig_table_offset
221 header_size + table_size, # translated_table_offset
222 hash_table_size, # hash_table_size
223 header_size + table_size * 2 # hash_table_offset
224 )
225 io.write(header.to_a.pack('a4V*'))
226
227 ary = translated_messages.to_a
228 ary.sort!{|a, b| a[0] <=> b[0]} # sort by original string
229
230 pos = header.hash_table_size * 4 + header.hash_table_offset
231
232 orig_table_data = Array.new()
233 ary.each{|item, _|
234 orig_table_data.push(item.bytesize)
235 orig_table_data.push(pos)
236 pos += item.bytesize + 1 # +1 is <NUL>
237 }
238 io.write(orig_table_data.pack('V*'))
239
240 trans_table_data = Array.new()
241 ary.each{|_, item|
242 trans_table_data.push(item.bytesize)
243 trans_table_data.push(pos)
244 pos += item.bytesize + 1 # +1 is <NUL>
245 }
246 io.write(trans_table_data.pack('V*'))
247
248 hash_tab = Array.new(hash_table_size)
249 j = 0
250 ary[0...size].each {|key, _|
251 hash_val = hash_string(key)
252 idx = hash_val % hash_table_size
253 if hash_tab[idx] != nil
254 incr = 1 + (hash_val % (hash_table_size - 2))
255 begin
256 if (idx >= hash_table_size - incr)
257 idx -= hash_table_size - incr
258 else
259 idx += incr
260 end
261 end until (hash_tab[idx] == nil)
262 end
263 hash_tab[idx] = j + 1
264 j += 1
265 }
266 hash_tab.collect!{|i| i ? i : 0}
267
268 io.write(hash_tab.pack('V*'))
269
270 ary.each{|item, _| io.write(item); io.write("\0") }
271 ary.each{|_, item| io.write(item); io.write("\0") }
272
273 self
274 end
275
276 def load_from_file(filename)
277 @filename = filename
278 begin
279 File.open(filename, 'rb'){|f| load_from_stream(f)}
280 rescue => e
281 e.set_backtrace("File: #{@filename}")
282 raise e
283 end
284 end
285
286 def save_to_file(filename)
287 File.open(filename, 'wb'){|f| save_to_stream(f)}
288 end
289
290 def set_comment(msgid_or_sym, comment)
291 #Do nothing
292 end
293
294 def plural_as_proc
295 unless @plural_proc
296 @plural_proc = Proc.new{|n| eval(@plural)}
297 begin
298 @plural_proc.call(1)
299 rescue
300 @plural_proc = Proc.new{|n| 0}
301 end
302 end
303 @plural_proc
304 end
305
306 attr_accessor :little_endian, :path, :last_modified
307 attr_reader :charset, :nplurals, :plural
308
309 private
310 def convert_encoding(string, original_string)
311 return string if @output_charset.nil? or @charset.nil?
312
313 if Encoding.find(@output_charset) == Encoding.find(@charset)
314 string.force_encoding(@output_charset)
315 return string
316 end
317
318 begin
319 string.encode(@output_charset, @charset)
320 rescue EncodingError
321 if $DEBUG
322 warn "@charset = ", @charset
323 warn "@output_charset = ", @output_charset
324 warn "msgid = ", original_string
325 warn "msgstr = ", string
326 end
327 string
328 end
329 end
330
331 def generate_original_string(msgid, options)
332 string = ""
333
334 msgctxt = options.delete(:msgctxt)
335 msgid_plural = options.delete(:msgid_plural)
336
337 string << msgctxt << "\004" unless msgctxt.nil?
338 string << msgid
339 string << "\000" << msgid_plural unless msgid_plural.nil?
340 string
341 end
342 end
343 end
344
345 # Test
346
347 if $0 == __FILE__
348 if (ARGV.include? "-h") or (ARGV.include? "--help")
349 STDERR.puts("mo.rb [filename.mo ...]")
350 exit
351 end
352
353 ARGV.each{ |item|
354 mo = GetText::MO.open(item)
355 puts "------------------------------------------------------------------"
356 puts "charset = \"#{mo.charset}\""
357 puts "nplurals = \"#{mo.nplurals}\""
358 puts "plural = \"#{mo.plural}\""
359 puts "------------------------------------------------------------------"
360 mo.each do |key, value|
361 puts "original message = #{key.inspect}"
362 puts "translated message = #{value.inspect}"
363 puts "--------------------------------------------------------------------"
364 end
365 }
366 end
+0
-275
vendor/gettext/po.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
4 #
5 # License: Ruby's or LGPL
6 #
7 # This library is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Lesser General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Lesser General Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 require "gettext/po_entry"
21
22 module GetText
23
24 # PO stores PO entries like Hash. Each key of {POEntry} is msgctxt
25 # and msgid.
26 # PO[msgctxt, msgid] returns the {POEntry} containing msgctxt and
27 # msgid.
28 # If you specify msgid only, msgctxt is treated as nonexistent.
29 #
30 # @since 2.3.4
31 class PO
32 include Enumerable
33
34 class NonExistentEntryError < StandardError
35 end
36
37 # @!attribute [rw] order
38 # The order is used to sort PO entries(objects of {POEntry}) in
39 # {#to_s}.
40 # @param [:reference, :msgid] order (:reference) The sort key.
41 #
42 # Use `:reference` for sorting by location that message is placed.
43 #
44 # Use `:msgid` for sorting by msgid alphabetical order.
45 #
46 # `:references` is deprecated since 3.0.4. It will be removed
47 # at 4.0.0. Use `:reference` instead.
48 #
49 # @return [Symbol] the name as order by sort.
50 attr_accessor :order
51
52 def initialize(order=nil)
53 @order = order || :reference
54 @entries = {}
55 end
56
57 # Returns {POEntry} containing msgctxt and msgid.
58 # If you specify one argument, it is treated as msgid.
59 # @overload [](msgid)
60 # @!macro [new] po.[].argument
61 # @param [String] msgid msgid contained returning {POEntry}.
62 # @return [POEntry]
63 # @!macro po.[].argument
64 # @overload [](msgctxt, msgid)
65 # @param [String] msgctxt msgctxt contained returning {POEntry}.
66 # @!macro po.[].argument
67 def [](msgctxt, msgid=nil)
68 if msgid.nil?
69 msgid = msgctxt
70 msgctxt = nil
71 end
72
73 @entries[[msgctxt, msgid]]
74 end
75
76 # Stores {POEntry} or msgstr binding msgctxt and msgid. If you
77 # specify msgstr, this method creates {POEntry} containing it.
78 # If you specify the two argument, the first argument is treated
79 # as msgid.
80 #
81 # @overload []=(msgid, po_entry)
82 # @!macro [new] po.store.entry.arguments
83 # @param [String] msgid msgid binded {POEntry}.
84 # @param [POEntry] po_entry stored {POEntry}.
85 # @!macro po.store.entry.arguments
86 # @overload []=(msgctxt, msgid, po_entry)
87 # @param [String] msgctxt msgctxt binded {POEntry}.
88 # @!macro po.store.entry.arguments
89 # @overload []=(msgid, msgstr)
90 # @!macro [new] po.store.msgstr.arguments
91 # @param [String] msgid msgid binded {POEntry}.
92 # @param [String] msgstr msgstr contained {POEntry} stored PO.
93 # This {POEntry} is generated in this method.
94 # @!macro po.store.msgstr.arguments
95 # @overload []=(msgctxt, msgid, msgstr)
96 # @param [String] msgctxt msgctxt binded {POEntry}.
97 # @!macro po.store.msgstr.arguments
98 def []=(*arguments)
99 case arguments.size
100 when 2
101 msgctxt = nil
102 msgid = arguments[0]
103 value = arguments[1]
104 when 3
105 msgctxt = arguments[0]
106 msgid = arguments[1]
107 value = arguments[2]
108 else
109 raise(ArgumentError,
110 "[]=: wrong number of arguments(#{arguments.size} for 2..3)")
111 end
112
113 id = [msgctxt, msgid]
114 if value.instance_of?(POEntry)
115 @entries[id] = value
116 return(value)
117 end
118
119 msgstr = value
120 if @entries.has_key?(id)
121 entry = @entries[id]
122 else
123 if msgctxt.nil?
124 entry = POEntry.new(:normal)
125 else
126 entry = POEntry.new(:msgctxt)
127 end
128 @entries[id] = entry
129 end
130 entry.msgctxt = msgctxt
131 entry.msgid = msgid
132 entry.msgstr = msgstr
133 entry
134 end
135
136 # Returns if PO stores {POEntry} containing msgctxt and msgid.
137 # If you specify one argument, it is treated as msgid and msgctxt
138 # is nil.
139 #
140 # @overload has_key?(msgid)
141 # @!macro [new] po.has_key?.arguments
142 # @param [String] msgid msgid contained {POEntry} checked if it be
143 # stored PO.
144 # @!macro po.has_key?.arguments
145 # @overload has_key?(msgctxt, msgid)
146 # @param [String] msgctxt msgctxt contained {POEntry} checked if
147 # it be stored PO.
148 # @!macro po.has_key?.arguments
149 def has_key?(*arguments)
150 case arguments.size
151 when 1
152 msgctxt = nil
153 msgid = arguments[0]
154 when 2
155 msgctxt = arguments[0]
156 msgid = arguments[1]
157 else
158 message = "has_key?: wrong number of arguments " +
159 "(#{arguments.size} for 1..2)"
160 raise(ArgumentError, message)
161 end
162 id = [msgctxt, msgid]
163 @entries.has_key?(id)
164 end
165
166 # Calls block once for each {POEntry} as a block parameter.
167 # @overload each(&block)
168 # @yield [entry]
169 # @yieldparam [POEntry] entry {POEntry} in PO.
170 # @overload each
171 # @return [Enumerator] Returns Enumerator for {POEntry}.
172 def each(&block)
173 @entries.each_value(&block)
174 end
175
176 # @return [Bool] `true` if there is no entry, `false` otherwise.
177 def empty?
178 @entries.empty?
179 end
180
181 # For {PoParer}.
182 def set_comment(msgid, comment, msgctxt=nil)
183 id = [msgctxt, msgid]
184 self[*id] = nil unless @entries.has_key?(id)
185 self[*id].comment = comment
186 end
187
188 # Formats each {POEntry} to the format of PO files and returns joined
189 # them.
190 # @see http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files
191 # The description for Format of PO in GNU gettext manual
192 # @param (see POEntry#to_s)
193 # @return [String] Formatted and joined PO entries. It is used for
194 # creating .po files.
195 def to_s(options={})
196 po_string = ""
197
198 header_entry = @entries[[nil, ""]]
199 unless header_entry.nil?
200 po_string << header_entry.to_s(options.merge(:max_line_width => nil))
201 end
202
203 content_entries = @entries.reject do |(_, msgid), _|
204 msgid == :last or msgid.empty?
205 end
206
207 sort(content_entries).each do |msgid, entry|
208 po_string << "\n" unless po_string.empty?
209 po_string << entry.to_s(options)
210 end
211
212 if @entries.has_key?([nil, :last])
213 po_string << "\n" unless po_string.empty?
214 po_string << @entries[[nil, :last]].to_s(options)
215 end
216
217 po_string
218 end
219
220 private
221 def sort(entries)
222 case @order
223 when :reference, :references # :references is deprecated.
224 sorted_entries = sort_by_reference(entries)
225 when :msgid
226 sorted_entries = sort_by_msgid(entries)
227 else
228 sorted_entries = entries.to_a
229 end
230 end
231
232 def sort_by_reference(entries)
233 entries.each do |_, entry|
234 entry.references = entry.references.sort do |reference, other|
235 compare_reference(reference, other)
236 end
237 end
238
239 entries.sort do |msgid_entry, other_msgid_entry|
240 # msgid_entry = [[msgctxt, msgid], POEntry]
241 entry_first_reference = msgid_entry[1].references.first
242 other_first_reference = other_msgid_entry[1].references.first
243 compare_reference(entry_first_reference, other_first_reference)
244 end
245 end
246
247 def compare_reference(reference, other)
248 entry_source, entry_line_number = split_reference(reference)
249 other_source, other_line_number = split_reference(other)
250
251 if entry_source != other_source
252 entry_source <=> other_source
253 else
254 entry_line_number <=> other_line_number
255 end
256 end
257
258 def split_reference(reference)
259 return ["", -1] if reference.nil?
260 if /\A(.+):(\d+?)\z/ =~ reference
261 [$1, $2.to_i]
262 else
263 [reference, -1]
264 end
265 end
266
267 def sort_by_msgid(entries)
268 entries.sort_by do |msgid_entry|
269 # msgid_entry = [[msgctxt, msgid], POEntry]
270 msgid_entry[0]
271 end
272 end
273 end
274 end
+0
-509
vendor/gettext/po_entry.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2010 masone (Christian Felder) <ema@rh-productions.ch>
4 # Copyright (C) 2009 Masao Mutoh
5 #
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "gettext/po_format"
22
23 module GetText
24 class ParseError < StandardError
25 end
26
27 # Contains data related to the expression or sentence that
28 # is to be translated.
29 class POEntry
30 class InvalidTypeError < StandardError
31 end
32
33 class NoMsgidError < StandardError
34 end
35
36 class NoMsgctxtError < StandardError
37 end
38
39 class NoMsgidPluralError < StandardError
40 end
41
42 PARAMS = {
43 :normal => [:msgid, :separator, :msgstr],
44 :plural => [:msgid, :msgid_plural, :separator, :msgstr],
45 :msgctxt => [:msgctxt, :msgid, :msgstr],
46 :msgctxt_plural => [:msgctxt, :msgid, :msgid_plural, :msgstr]
47 }
48
49 # Required
50 attr_reader :type # :normal, :plural, :msgctxt, :msgctxt_plural
51 attr_accessor :msgid
52 attr_accessor :msgstr
53 # Options
54 attr_accessor :msgid_plural
55 attr_accessor :separator
56 attr_accessor :msgctxt
57 attr_accessor :references # ["file1:line1", "file2:line2", ...]
58 attr_accessor :translator_comment
59 attr_accessor :extracted_comment
60 # @return [Array<String>] The flags for this PO entry.
61 # @since 3.0.4
62 attr_accessor :flags
63 attr_accessor :previous
64 attr_accessor :comment
65
66 # Create the object. +type+ should be :normal, :plural, :msgctxt or :msgctxt_plural.
67 def initialize(type)
68 self.type = type
69 @translator_comment = nil
70 @extracted_comment = nil
71 @references = []
72 @flags = []
73 @previous = nil
74 @msgctxt = nil
75 @msgid = nil
76 @msgid_plural = nil
77 @msgstr = nil
78 end
79
80 # Support for extracted comments. Explanation s.
81 # http://www.gnu.org/software/gettext/manual/gettext.html#Names
82 # @return [void]
83 def add_comment(new_comment)
84 if (new_comment and ! new_comment.empty?)
85 @extracted_comment ||= ""
86 @extracted_comment << "\n" unless @extracted_comment.empty?
87 @extracted_comment << new_comment
88 end
89 end
90
91 # @return [String, nil] The flag of the PO entry.
92 # @deprecated Since 3.0.4. Use {#flags} instead.
93 def flag
94 @flags.first
95 end
96
97 # Set the new flag for the PO entry.
98 #
99 # @param [String, nil] flag The new flag.
100 # @deprecated Since 3.0.4. Use {#flags=} instead.
101 def flag=(flag)
102 if flag.nil?
103 @flags = []
104 else
105 @flags = [flag]
106 end
107 end
108
109 # Checks if the self has same attributes as other.
110 def ==(other)
111 not other.nil? and
112 type == other.type and
113 msgid == other.msgid and
114 msgstr == other.msgstr and
115 msgid_plural == other.msgid_plural and
116 separator == other.separator and
117 msgctxt == other.msgctxt and
118 translator_comment == other.translator_comment and
119 extracted_comment == other.extracted_comment and
120 references == other.references and
121 flags == other.flags and
122 previous == other.previous and
123 comment == other.comment
124 end
125
126 def type=(type)
127 unless PARAMS.has_key?(type)
128 raise(InvalidTypeError, "\"%s\" is invalid type." % type)
129 end
130 @type = type
131 @param_type = PARAMS[@type]
132 end
133
134 # Checks if the other translation target is mergeable with
135 # the current one. Relevant are msgid and translation context (msgctxt).
136 def mergeable?(other)
137 other && other.msgid == self.msgid && other.msgctxt == self.msgctxt
138 end
139
140 # Merges two translation targets with the same msgid and returns the merged
141 # result. If one is declared as plural and the other not, then the one
142 # with the plural wins.
143 def merge(other)
144 return self unless other
145 unless mergeable?(other)
146 message = "Translation targets do not match: \n" +
147 " self: #{self.inspect}\n other: '#{other.inspect}'"
148 raise ParseError, message
149 end
150 if other.msgid_plural && !msgid_plural
151 res = other
152 unless res.references.include?(references[0])
153 res.references += references
154 res.add_comment(extracted_comment)
155 end
156 else
157 res = self
158 unless res.references.include?(other.references[0])
159 res.references += other.references
160 res.add_comment(other.extracted_comment)
161 end
162 end
163 res
164 end
165
166 # Format the po entry in PO format.
167 #
168 # @param [Hash] options
169 # @option options (see Formatter#initialize)
170 def to_s(options={})
171 raise(NoMsgidError, "msgid is nil.") unless @msgid
172
173 formatter = Formatter.new(self, options)
174 formatter.format
175 end
176
177 # Returns true if the type is kind of msgctxt.
178 def msgctxt?
179 [:msgctxt, :msgctxt_plural].include?(@type)
180 end
181
182 # Returns true if the type is kind of plural.
183 def plural?
184 [:plural, :msgctxt_plural].include?(@type)
185 end
186
187 # @return true if the entry is header entry, false otherwise.
188 # Header entry is normal type and has empty msgid.
189 def header?
190 @type == :normal and @msgid == ""
191 end
192
193 # @return true if the entry is obsolete entry, false otherwise.
194 # Obsolete entry is normal type and has :last msgid.
195 def obsolete?
196 @type == :normal and @msgid == :last
197 end
198
199 # @return true if the entry is fuzzy entry, false otherwise.
200 # Fuzzy entry has "fuzzy" flag.
201 def fuzzy?
202 @flags.include?("fuzzy")
203 end
204
205 # @return true if the entry is translated entry, false otherwise.
206 def translated?
207 return false if fuzzy?
208 return false if @msgstr.nil? or @msgstr.empty?
209 true
210 end
211
212 def [](number)
213 param = @param_type[number]
214 raise ParseError, 'no more string parameters expected' unless param
215 send param
216 end
217
218 private
219
220 # sets or extends the value of a translation target params like msgid,
221 # msgctxt etc.
222 # param is symbol with the name of param
223 # value - new value
224 def set_value(param, value)
225 send "#{param}=", (send(param) || '') + value
226 end
227
228 class Formatter
229 class << self
230 def escape(string)
231 return "" if string.nil?
232
233 string.gsub(/([\\"\t\n])/) do
234 special_character = $1
235 case special_character
236 when "\t"
237 "\\t"
238 when "\n"
239 "\\n"
240 else
241 "\\#{special_character}"
242 end
243 end
244 end
245 end
246
247 include POFormat
248
249 DEFAULT_MAX_LINE_WIDTH = 78
250
251 # @param [POEntry] entry The entry to be formatted.
252 # @param [Hash] options
253 # @option options [Bool] :include_translator_comment (true)
254 # Includes translator comments in formatted string if true.
255 # @option options [Bool] :include_extracted_comment (true)
256 # Includes extracted comments in formatted string if true.
257 # @option options [Bool] :include_reference_comment (true)
258 # Includes reference comments in formatted string if true.
259 # @option options [Bool] :include_flag_comment (true)
260 # Includes flag comments in formatted string if true.
261 # @option options [Bool] :include_previous_comment (true)
262 # Includes previous comments in formatted string if true.
263 # @option options [Bool] :include_all_comments (true)
264 # Includes all comments in formatted string if true.
265 # Other specific `:include_XXX` options get preference over
266 # this option.
267 # You can remove all comments by specifying this option as
268 # false and omitting other `:include_XXX` options.
269 # @option options [Integer] :max_line_width (78)
270 # Wraps long lines that is longer than the `:max_line_width`.
271 # Don't break long lines if `:max_line_width` is less than 0
272 # such as `-1`.
273 # @option options [Encoding] :encoding (nil)
274 # Encodes to the specific encoding.
275 def initialize(entry, options={})
276 @entry = entry
277 @options = normalize_options(options)
278 end
279
280 def format
281 if @entry.obsolete?
282 return format_obsolete_comment(@entry.comment)
283 end
284
285 str = format_comments
286
287 # msgctxt, msgid, msgstr
288 if @entry.msgctxt?
289 if @entry.msgctxt.nil?
290 no_msgctxt_message = "This POEntry is a kind of msgctxt " +
291 "but the msgctxt property is nil. " +
292 "msgid: #{@entry.msgid}"
293 raise(NoMsgctxtError, no_msgctxt_message)
294 end
295 str << "msgctxt " << format_message(@entry.msgctxt)
296 end
297
298 str << "msgid " << format_message(@entry.msgid)
299 if @entry.plural?
300 if @entry.msgid_plural.nil?
301 no_plural_message = "This POEntry is a kind of plural " +
302 "but the msgid_plural property is nil. " +
303 "msgid: #{@entry.msgid}"
304 raise(NoMsgidPluralError, no_plural_message)
305 end
306
307 str << "msgid_plural " << format_message(@entry.msgid_plural)
308
309 if @entry.msgstr.nil?
310 str << "msgstr[0] \"\"\n"
311 str << "msgstr[1] \"\"\n"
312 else
313 msgstrs = @entry.msgstr.split("\000", -1)
314 msgstrs.each_with_index do |msgstr, index|
315 str << "msgstr[#{index}] " << format_message(msgstr)
316 end
317 end
318 else
319 str << "msgstr "
320 str << format_message(@entry.msgstr)
321 end
322
323 encode(str)
324 end
325
326 private
327 def normalize_options(options)
328 options = options.dup
329 include_comment_keys = [
330 :include_translator_comment,
331 :include_extracted_comment,
332 :include_reference_comment,
333 :include_flag_comment,
334 :include_previous_comment,
335 ]
336 if options[:include_all_comments].nil?
337 options[:include_all_comments] = true
338 end
339 default_include_comment_value = options[:include_all_comments]
340 include_comment_keys.each do |key|
341 options[key] = default_include_comment_value if options[key].nil?
342 end
343 options[:max_line_width] ||= DEFAULT_MAX_LINE_WIDTH
344 options
345 end
346
347 def include_translator_comment?
348 @options[:include_translator_comment]
349 end
350
351 def include_extracted_comment?
352 @options[:include_extracted_comment]
353 end
354
355 def include_reference_comment?
356 @options[:include_reference_comment]
357 end
358
359 def include_flag_comment?
360 @options[:include_flag_comment]
361 end
362
363 def include_previous_comment?
364 @options[:include_previous_comment]
365 end
366
367 def format_comments
368 formatted_comment = ""
369 if include_translator_comment?
370 formatted_comment << format_translator_comment
371 end
372 if include_extracted_comment?
373 formatted_comment << format_extracted_comment
374 end
375 if include_reference_comment?
376 formatted_comment << format_reference_comment
377 end
378 if include_flag_comment?
379 formatted_comment << format_flag_comment
380 end
381 if include_previous_comment?
382 formatted_comment << format_previous_comment
383 end
384 formatted_comment
385 end
386
387 def format_translator_comment
388 format_comment("#", @entry.translator_comment)
389 end
390
391 def format_extracted_comment
392 format_comment(EXTRACTED_COMMENT_MARK, @entry.extracted_comment)
393 end
394
395 def format_reference_comment
396 max_line_width = @options[:max_line_width]
397 formatted_reference = ""
398 if not @entry.references.nil? and not @entry.references.empty?
399 formatted_reference << REFERENCE_COMMENT_MARK
400 line_width = 2
401 @entry.references.each do |reference|
402 if max_line_width > 0 and
403 line_width + reference.size > max_line_width
404 formatted_reference << "\n"
405 formatted_reference << "#{REFERENCE_COMMENT_MARK} #{reference}"
406 line_width = 3 + reference.size
407 else
408 formatted_reference << " #{reference}"
409 line_width += 1 + reference.size
410 end
411 end
412
413 formatted_reference << "\n"
414 end
415 formatted_reference
416 end
417
418 def format_flag_comment
419 formatted_flags = ""
420 @entry.flags.each do |flag|
421 formatted_flags << format_comment(FLAG_MARK, flag)
422 end
423 formatted_flags
424 end
425
426 def format_previous_comment
427 format_comment(PREVIOUS_COMMENT_MARK, @entry.previous)
428 end
429
430 def format_comment(mark, comment)
431 return "" if comment.nil?
432
433 formatted_comment = ""
434 comment.each_line do |comment_line|
435 if comment_line == "\n"
436 formatted_comment << "#{mark}\n"
437 else
438 formatted_comment << "#{mark} #{comment_line.strip}\n"
439 end
440 end
441 formatted_comment
442 end
443
444 def format_obsolete_comment(comment)
445 mark = "#~"
446 return "" if comment.nil?
447
448 formatted_comment = ""
449 comment.each_line do |comment_line|
450 if /\A#[^~]/ =~ comment_line or comment_line.start_with?(mark)
451 formatted_comment << "#{comment_line.chomp}\n"
452 elsif comment_line == "\n"
453 formatted_comment << "\n"
454 else
455 formatted_comment << "#{mark} #{comment_line.strip}\n"
456 end
457 end
458 formatted_comment
459 end
460
461 def format_message(message)
462 empty_formatted_message = "\"\"\n"
463 return empty_formatted_message if message.nil?
464
465 chunks = wrap_message(message)
466 return empty_formatted_message if chunks.empty?
467
468 formatted_message = ""
469 if chunks.size > 1 or chunks.first.end_with?("\n")
470 formatted_message << empty_formatted_message
471 end
472 chunks.each do |chunk|
473 formatted_message << "\"#{escape(chunk)}\"\n"
474 end
475 formatted_message
476 end
477
478 def escape(string)
479 self.class.escape(string)
480 end
481
482 def wrap_message(message)
483 return [message] if message.empty?
484
485 max_line_width = @options[:max_line_width]
486
487 chunks = []
488 message.each_line do |line|
489 if max_line_width <= 0
490 chunks << line
491 else
492 # TODO: use character width instead of the number of characters
493 line.scan(/.{1,#{max_line_width}}/m) do |chunk|
494 chunks << chunk
495 end
496 end
497 end
498 chunks
499 end
500
501 def encode(string)
502 encoding = @options[:encoding]
503 return string if encoding.nil?
504 string.encode(encoding)
505 end
506 end
507 end
508 end
+0
-28
vendor/gettext/po_format.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
3 #
4 # License: Ruby's or LGPL
5 #
6 # This library is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 module GetText
20 module POFormat
21 TRANSLATOR_COMMENT_MARK = "# "
22 EXTRACTED_COMMENT_MARK = "#."
23 FLAG_MARK = "#,"
24 PREVIOUS_COMMENT_MARK = "#|"
25 REFERENCE_COMMENT_MARK = "#:"
26 end
27 end
+0
-500
vendor/gettext/po_parser.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # po_parser.rb - Generate a .mo
3 #
4 # Copyright (C) 2003-2009 Masao Mutoh <mutomasa at gmail.com>
5 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
6 #
7 # You may redistribute it and/or modify it under the same
8 # license terms as Ruby or LGPL.
9
10 #
11 # DO NOT MODIFY!!!!
12 # This file is automatically generated by Racc 1.4.11
13 # from Racc grammer file "".
14 #
15
16 require 'racc/parser.rb'
17
18 require "gettext/po"
19
20 module GetText
21 class POParser < Racc::Parser
22
23 module_eval(<<'...end po_parser.ry/module_eval...', 'po_parser.ry', 123)
24 if GetText.respond_to?(:bindtextdomain)
25 include GetText
26 GetText.bindtextdomain("gettext")
27 else
28 def _(message_id)
29 message_id
30 end
31 private :_
32 end
33
34 attr_writer :ignore_fuzzy, :report_warning
35 def initialize
36 @ignore_fuzzy = true
37 @report_warning = true
38 end
39
40 def ignore_fuzzy?
41 @ignore_fuzzy
42 end
43
44 def report_warning?
45 @report_warning
46 end
47
48 def unescape(orig)
49 ret = orig.gsub(/\\n/, "\n")
50 ret.gsub!(/\\t/, "\t")
51 ret.gsub!(/\\r/, "\r")
52 ret.gsub!(/\\"/, "\"")
53 ret
54 end
55 private :unescape
56
57 def unescape_string(string)
58 string.gsub(/\\\\/, "\\")
59 end
60 private :unescape_string
61
62 def parse(str, data)
63 @translator_comments = []
64 @extracted_comments = []
65 @references = []
66 @flags = []
67 @previous = []
68 @comments = []
69 @data = data
70 @fuzzy = false
71 @msgctxt = nil
72 @msgid_plural = nil
73
74 str.strip!
75 @q = []
76 until str.empty? do
77 case str
78 when /\A\s+/
79 str = $'
80 when /\Amsgctxt/
81 @q.push [:MSGCTXT, $&]
82 str = $'
83 when /\Amsgid_plural/
84 @q.push [:MSGID_PLURAL, $&]
85 str = $'
86 when /\Amsgid/
87 @q.push [:MSGID, $&]
88 str = $'
89 when /\Amsgstr/
90 @q.push [:MSGSTR, $&]
91 str = $'
92 when /\A\[(\d+)\]/
93 @q.push [:PLURAL_NUM, $1]
94 str = $'
95 when /\A\#~(.*)/
96 if report_warning?
97 $stderr.print _("Warning: obsolete msgid exists.\n")
98 $stderr.print " #{$&}\n"
99 end
100 @q.push [:COMMENT, $&]
101 str = $'
102 when /\A\#(.*)/
103 @q.push [:COMMENT, $&]
104 str = $'
105 when /\A\"(.*)\"/
106 @q.push [:STRING, unescape_string($1)]
107 str = $'
108 else
109 #c = str[0,1]
110 #@q.push [:STRING, c]
111 str = str[1..-1]
112 end
113 end
114 @q.push [false, "$end"]
115 if $DEBUG
116 @q.each do |a,b|
117 puts "[#{a}, #{b}]"
118 end
119 end
120 @yydebug = true if $DEBUG
121 do_parse
122
123 if @comments.size > 0
124 @data.set_comment(:last, @comments.join("\n"))
125 end
126 @data
127 end
128
129 def next_token
130 @q.shift
131 end
132
133 def on_message(msgid, msgstr)
134 msgstr = nil if msgstr.empty?
135
136 if @data.instance_of?(PO)
137 type = detect_entry_type
138 entry = POEntry.new(type)
139 entry.translator_comment = format_comment(@translator_comments)
140 entry.extracted_comment = format_comment(@extracted_comments)
141 entry.flags = @flags
142 entry.previous = format_comment(@previous)
143 entry.references = @references
144 entry.msgctxt = @msgctxt
145 entry.msgid = msgid
146 entry.msgid_plural = @msgid_plural
147 entry.msgstr = msgstr
148
149 @data[@msgctxt, msgid] = entry
150 else
151 options = {}
152 options[:msgctxt] = @msgctxt
153 options[:msgid_plural] = @msgid_plural
154 @data.store(msgid, msgstr, options)
155 @data.set_comment(msgid, format_comment(@comments))
156 end
157
158 @translator_comments = []
159 @extracted_comments = []
160 @references = []
161 @flags = []
162 @previous = []
163 @references = []
164 @comments.clear
165 @msgctxt = nil
166 @msgid_plural = nil
167 end
168
169 def format_comment(comments)
170 return "" if comments.empty?
171
172 comment = comments.join("\n")
173 comment << "\n" if comments.last.empty?
174 comment
175 end
176
177 def on_comment(comment)
178 @fuzzy = true if (/fuzzy/ =~ comment)
179 if @data.instance_of?(PO)
180 if comment == "#"
181 @translator_comments << ""
182 elsif /\A(#.)\s*(.*)\z/ =~ comment
183 mark = $1
184 content = $2
185 case mark
186 when POFormat::TRANSLATOR_COMMENT_MARK
187 @translator_comments << content
188 when POFormat::EXTRACTED_COMMENT_MARK
189 @extracted_comments << content
190 when POFormat::REFERENCE_COMMENT_MARK
191 @references.concat(parse_references_line(content))
192 when POFormat::FLAG_MARK
193 @flags.concat(parse_flags_line(content))
194 when POFormat::PREVIOUS_COMMENT_MARK
195 @previous << content
196 else
197 @comments << comment
198 end
199 end
200 else
201 @comments << comment
202 end
203 end
204
205 def parse_file(po_file, data)
206 args = [ po_file ]
207 # In Ruby 1.9, we must detect proper encoding of a PO file.
208 if String.instance_methods.include?(:encode)
209 encoding = detect_file_encoding(po_file)
210 args << "r:#{encoding}"
211 end
212 @po_file = po_file
213 parse(File.open(*args) {|io| io.read }, data)
214 end
215
216 private
217 def detect_file_encoding(po_file)
218 open(po_file, :encoding => "ASCII-8BIT") do |input|
219 in_header = false
220 input.each_line do |line|
221 case line.chomp
222 when /\Amsgid\s+"(.*)"\z/
223 id = $1
224 break unless id.empty?
225 in_header = true
226 when /\A"Content-Type:.*\scharset=(.*)\\n"\z/
227 charset = $1
228 next unless in_header
229 break if template_charset?(charset)
230 return Encoding.find(charset)
231 end
232 end
233 end
234 Encoding.default_external
235 end
236
237 def template_charset?(charset)
238 charset == "CHARSET"
239 end
240
241 def detect_entry_type
242 if @msgctxt.nil?
243 if @msgid_plural.nil?
244 :normal
245 else
246 :plural
247 end
248 else
249 if @msgid_plural.nil?
250 :msgctxt
251 else
252 :msgctxt_plural
253 end
254 end
255 end
256
257 def parse_references_line(line)
258 line.split(/\s+/)
259 end
260
261 def parse_flags_line(line)
262 line.split(/\s+/)
263 end
264 ...end po_parser.ry/module_eval...
265 ##### State transition tables begin ###
266
267 racc_action_table = [
268 2, 13, 10, 9, 6, 17, 16, 15, 22, 15,
269 15, 13, 13, 13, 15, 11, 22, 24, 13, 15 ]
270
271 racc_action_check = [
272 1, 17, 1, 1, 1, 14, 14, 14, 19, 19,
273 12, 6, 16, 9, 18, 2, 20, 22, 24, 25 ]
274
275 racc_action_pointer = [
276 nil, 0, 15, nil, nil, nil, 4, nil, nil, 6,
277 nil, nil, 3, nil, 0, nil, 5, -6, 7, 2,
278 10, nil, 9, nil, 11, 12 ]
279
280 racc_action_default = [
281 -1, -16, -16, -2, -3, -4, -16, -6, -7, -16,
282 -13, 26, -5, -15, -16, -14, -16, -16, -8, -16,
283 -9, -11, -16, -10, -16, -12 ]
284
285 racc_goto_table = [
286 12, 21, 23, 14, 4, 5, 3, 7, 8, 20,
287 18, 19, 1, nil, nil, nil, nil, nil, 25 ]
288
289 racc_goto_check = [
290 5, 9, 9, 5, 3, 4, 2, 6, 7, 8,
291 5, 5, 1, nil, nil, nil, nil, nil, 5 ]
292
293 racc_goto_pointer = [
294 nil, 12, 5, 3, 4, -6, 6, 7, -10, -18 ]
295
296 racc_goto_default = [
297 nil, nil, nil, nil, nil, nil, nil, nil, nil, nil ]
298
299 racc_reduce_table = [
300 0, 0, :racc_error,
301 0, 10, :_reduce_none,
302 2, 10, :_reduce_none,
303 2, 10, :_reduce_none,
304 2, 10, :_reduce_none,
305 2, 12, :_reduce_5,
306 1, 13, :_reduce_none,
307 1, 13, :_reduce_none,
308 4, 15, :_reduce_8,
309 5, 16, :_reduce_9,
310 2, 17, :_reduce_10,
311 1, 17, :_reduce_none,
312 3, 18, :_reduce_12,
313 1, 11, :_reduce_13,
314 2, 14, :_reduce_14,
315 1, 14, :_reduce_15 ]
316
317 racc_reduce_n = 16
318
319 racc_shift_n = 26
320
321 racc_token_table = {
322 false => 0,
323 :error => 1,
324 :COMMENT => 2,
325 :MSGID => 3,
326 :MSGCTXT => 4,
327 :MSGID_PLURAL => 5,
328 :MSGSTR => 6,
329 :STRING => 7,
330 :PLURAL_NUM => 8 }
331
332 racc_nt_base = 9
333
334 racc_use_result_var = true
335
336 Racc_arg = [
337 racc_action_table,
338 racc_action_check,
339 racc_action_default,
340 racc_action_pointer,
341 racc_goto_table,
342 racc_goto_check,
343 racc_goto_default,
344 racc_goto_pointer,
345 racc_nt_base,
346 racc_reduce_table,
347 racc_token_table,
348 racc_shift_n,
349 racc_reduce_n,
350 racc_use_result_var ]
351
352 Racc_token_to_s_table = [
353 "$end",
354 "error",
355 "COMMENT",
356 "MSGID",
357 "MSGCTXT",
358 "MSGID_PLURAL",
359 "MSGSTR",
360 "STRING",
361 "PLURAL_NUM",
362 "$start",
363 "msgfmt",
364 "comment",
365 "msgctxt",
366 "message",
367 "string_list",
368 "single_message",
369 "plural_message",
370 "msgstr_plural",
371 "msgstr_plural_line" ]
372
373 Racc_debug_parser = true
374
375 ##### State transition tables end #####
376
377 # reduce 0 omitted
378
379 # reduce 1 omitted
380
381 # reduce 2 omitted
382
383 # reduce 3 omitted
384
385 # reduce 4 omitted
386
387 module_eval(<<'.,.,', 'po_parser.ry', 26)
388 def _reduce_5(val, _values, result)
389 @msgctxt = unescape(val[1])
390
391 result
392 end
393 .,.,
394
395 # reduce 6 omitted
396
397 # reduce 7 omitted
398
399 module_eval(<<'.,.,', 'po_parser.ry', 38)
400 def _reduce_8(val, _values, result)
401 msgid_raw = val[1]
402 msgid = unescape(msgid_raw)
403 msgstr = unescape(val[3])
404 use_message_p = true
405 if @fuzzy and not msgid.empty?
406 use_message_p = (not ignore_fuzzy?)
407 if report_warning?
408 if ignore_fuzzy?
409 $stderr.print _("Warning: fuzzy message was ignored.\n")
410 else
411 $stderr.print _("Warning: fuzzy message was used.\n")
412 end
413 $stderr.print " #{@po_file}: msgid '#{msgid_raw}'\n"
414 end
415 end
416 @fuzzy = false
417 on_message(msgid, msgstr) if use_message_p
418 result = ""
419
420 result
421 end
422 .,.,
423
424 module_eval(<<'.,.,', 'po_parser.ry', 61)
425 def _reduce_9(val, _values, result)
426 if @fuzzy and ignore_fuzzy?
427 if val[1] != ""
428 if report_warning?
429 $stderr.print _("Warning: fuzzy message was ignored.\n")
430 $stderr.print "msgid = '#{val[1]}\n"
431 end
432 else
433 on_message("", unescape(val[3]))
434 end
435 @fuzzy = false
436 else
437 @msgid_plural = unescape(val[3])
438 on_message(unescape(val[1]), unescape(val[4]))
439 end
440 result = ""
441
442 result
443 end
444 .,.,
445
446 module_eval(<<'.,.,', 'po_parser.ry', 82)
447 def _reduce_10(val, _values, result)
448 if val[0].size > 0
449 result = val[0] + "\000" + val[1]
450 else
451 result = ""
452 end
453
454 result
455 end
456 .,.,
457
458 # reduce 11 omitted
459
460 module_eval(<<'.,.,', 'po_parser.ry', 94)
461 def _reduce_12(val, _values, result)
462 result = val[2]
463
464 result
465 end
466 .,.,
467
468 module_eval(<<'.,.,', 'po_parser.ry', 101)
469 def _reduce_13(val, _values, result)
470 on_comment(val[0])
471
472 result
473 end
474 .,.,
475
476 module_eval(<<'.,.,', 'po_parser.ry', 109)
477 def _reduce_14(val, _values, result)
478 result = val.delete_if{|item| item == ""}.join
479
480 result
481 end
482 .,.,
483
484 module_eval(<<'.,.,', 'po_parser.ry', 113)
485 def _reduce_15(val, _values, result)
486 result = val[0]
487
488 result
489 end
490 .,.,
491
492 def _reduce_none(val, _values, result)
493 val[0]
494 end
495
496 end # class POParser
497 end # module GetText
498
499
+0
-183
vendor/gettext/text_domain.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 text_domain.rb - GetText::TextDomain
4
5 Copyright (C) 2001-2009 Masao Mutoh
6 Copyright (C) 2001-2003 Masahiro Sakai
7
8 Masahiro Sakai <s01397ms@sfc.keio.ac.jp>
9 Masao Mutoh <mutomasa at gmail.com>
10
11 You may redistribute it and/or modify it under the same
12 license terms as Ruby or LGPL.
13 =end
14
15 require 'gettext/mo'
16 require 'gettext/locale_path'
17
18 module GetText
19 # GetText::TextDomain class manages mo-files of a text domain.
20 #
21 # Usually, you don't need to use this class directly.
22 #
23 # Notice: This class is unstable. APIs will be changed.
24 class TextDomain
25
26 attr_reader :output_charset
27 attr_reader :mofiles
28 attr_reader :name
29
30 @@cached = ! $DEBUG
31 # Cache the mo-file or not.
32 # Default is true. If $DEBUG is set then false.
33 def self.cached?
34 @@cached
35 end
36
37 # Set to cache the mo-file or not.
38 # * val: true if cached, otherwise false.
39 def self.cached=(val)
40 @@cached = val
41 end
42
43 # Creates a new GetText::TextDomain.
44 # * name: the text domain name.
45 # * topdir: the locale path ("%{topdir}/%{lang}/LC_MESSAGES/%{name}.mo") or nil.
46 # * output_charset: output charset.
47 # * Returns: a newly created GetText::TextDomain object.
48 def initialize(name, topdir = nil, output_charset = nil)
49 @name, @output_charset = name, output_charset
50
51 @locale_path = LocalePath.new(@name, topdir)
52 @mofiles = {}
53 end
54
55 # Translates the translated string.
56 # * lang: Locale::Tag::Simple's subclass.
57 # * msgid: the original message.
58 # * Returns: the translated string or nil.
59 def translate_singular_message(lang, msgid)
60 return "" if msgid.nil?
61
62 lang_key = lang.to_s
63
64 mo = nil
65 if self.class.cached?
66 mo = @mofiles[lang_key]
67 end
68 unless mo
69 mo = load_mo(lang)
70 end
71
72 if (! mo) or (mo ==:empty)
73 return nil
74 end
75
76 return mo[msgid] if mo.has_key?(msgid)
77
78 ret = nil
79 if msgid.include?("\000")
80 # Check "aaa\000bbb" and show warning but return the singular part.
81 msgid_single = msgid.split("\000")[0]
82 msgid_single_prefix_re = /^#{Regexp.quote(msgid_single)}\000/
83 mo.each do |key, val|
84 if msgid_single_prefix_re =~ key
85 # Usually, this is not caused to make po-files from rgettext.
86 separated_msgid = msgid.gsub(/\000/, '", "')
87 duplicated_msgid = key.gsub(/\000/, '", "')
88 warn("Warning: " +
89 "n_(\"#{separated_msgid}\") and " +
90 "n_(\"#{duplicated_msgid}\") " +
91 "are duplicated.")
92 ret = val
93 break
94 end
95 end
96 else
97 msgid_prefix_re = /^#{Regexp.quote(msgid)}\000/
98 mo.each do |key, val|
99 if msgid_prefix_re =~ key
100 ret = val.split("\000")[0]
101 break
102 end
103 end
104 end
105 ret
106 end
107
108 DEFAULT_PLURAL_CALC = Proc.new {|n| n != 1}
109 DEFAULT_SINGLE_CALC = Proc.new {|n| 0}
110
111 # Translates the translated string.
112 # * lang: Locale::Tag::Simple's subclass.
113 # * msgid: the original message.
114 # * msgid_plural: the original message(plural).
115 # * Returns: the translated string as an Array ([[msgstr1, msgstr2, ...], cond]) or nil.
116 def translate_plural_message(lang, msgid, msgid_plural) #:nodoc:
117 key = msgid + "\000" + msgid_plural
118 msg = translate_singular_message(lang, key)
119 ret = nil
120 if ! msg
121 ret = nil
122 elsif msg.include?("\000")
123 # [[msgstr[0], msgstr[1], msgstr[2],...], cond]
124 mo = @mofiles[lang.to_posix.to_s]
125 cond = (mo and mo != :empty) ? mo.plural_as_proc : DEFAULT_PLURAL_CALC
126 ret = [msg.split("\000"), cond]
127 else
128 ret = [[msg], DEFAULT_SINGLE_CALC]
129 end
130 ret
131 end
132
133 # Clear cached mofiles.
134 def clear
135 @mofiles = {}
136 end
137
138 # Set output_charset.
139 # * charset: output charset.
140 def output_charset=(charset)
141 @output_charset = charset
142 clear
143 end
144
145 private
146 # Load a mo-file from the file.
147 # lang is the subclass of Locale::Tag::Simple.
148 def load_mo(lang)
149 lang = lang.to_posix unless lang.kind_of? Locale::Tag::Posix
150 lang_key = lang.to_s
151
152 mo = @mofiles[lang_key]
153 if mo
154 if mo == :empty
155 return :empty
156 elsif ! self.class.cached?
157 mo.update!
158 end
159 return mo
160 end
161
162 path = @locale_path.current_path(lang)
163
164 if path
165 charset = @output_charset || lang.charset || Locale.charset || "UTF-8"
166 charset = normalize_charset(charset)
167 @mofiles[lang_key] = MO.open(path, charset)
168 else
169 @mofiles[lang_key] = :empty
170 end
171 end
172
173 def normalize_charset(charset)
174 case charset
175 when /\Autf8\z/i
176 "UTF-8"
177 else
178 charset
179 end
180 end
181 end
182 end
+0
-26
vendor/gettext/text_domain_group.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 gettext/text_domain_group - GetText::TextDomainGroup class
4
5 Copyright (C) 2009 Masao Mutoh
6
7 You may redistribute it and/or modify it under the same
8 license terms as Ruby or LGPL.
9
10 =end
11
12 module GetText
13
14 class TextDomainGroup
15 attr_reader :text_domains
16
17 def initialize
18 @text_domains = []
19 end
20
21 def add(text_domain)
22 @text_domains.unshift(text_domain) unless @text_domains.include? text_domain
23 end
24 end
25 end
+0
-229
vendor/gettext/text_domain_manager.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 gettext/text_domain_manager - GetText::TextDomainManager class
4
5 Copyright (C) 2009 Masao Mutoh
6
7 You may redistribute it and/or modify it under the same
8 license terms as Ruby or LGPL.
9
10 =end
11
12 require 'gettext/class_info'
13 require 'gettext/text_domain'
14 require 'gettext/text_domain_group'
15
16 module GetText
17
18 module TextDomainManager
19
20 @@text_domain_pool = {}
21 @@text_domain_group_pool = {}
22
23 @@output_charset = nil
24 @@gettext_classes = []
25
26 @@singular_message_cache = {}
27 @@plural_message_cache = {}
28 @@cached = ! $DEBUG
29
30 extend self
31
32 # Find text domain by name
33 def text_domain_pool(domainname)
34 @@text_domain_pool[domainname]
35 end
36
37 # Set the value whether cache messages or not.
38 # true to cache messages, otherwise false.
39 #
40 # Default is true. If $DEBUG is false, messages are not checked even if
41 # this value is true.
42 def cached=(val)
43 @@cached = val
44 TextDomain.cached = val
45 end
46
47 # Return the cached value.
48 def cached?
49 TextDomain.cached?
50 end
51
52 # Gets the output charset.
53 def output_charset
54 @@output_charset
55 end
56
57 # Sets the output charset.The program can have a output charset.
58 def output_charset=(charset)
59 @@output_charset = charset
60 @@text_domain_pool.each do |key, text_domain|
61 text_domain.output_charset = charset
62 end
63 end
64
65 # bind text domain to the class.
66 def bind_to(klass, domainname, options = {})
67 warn "Bind the domain '#{domainname}' to '#{klass}'. " if $DEBUG
68
69 charset = options[:output_charset] || self.output_charset
70 text_domain = create_or_find_text_domain(domainname,options[:path],charset)
71 target_klass = ClassInfo.normalize_class(klass)
72 create_or_find_text_domain_group(target_klass).add(text_domain)
73 @@gettext_classes << target_klass unless @@gettext_classes.include? target_klass
74
75 text_domain
76 end
77
78 def each_text_domains(klass) #:nodoc:
79 lang = Locale.candidates[0]
80 ClassInfo.related_classes(klass, @@gettext_classes).each do |target|
81 if group = @@text_domain_group_pool[target]
82 group.text_domains.each do |text_domain|
83 yield text_domain, lang
84 end
85 end
86 end
87 end
88
89 # Translates msgid, but if there are no localized text,
90 # it returns a last part of msgid separeted "div" or whole of the msgid with no "div".
91 #
92 # * msgid: the message id.
93 # * div: separator or nil.
94 # * Returns: the localized text by msgid. If there are no localized text,
95 # it returns a last part of msgid separeted "div".
96 def translate_singular_message(klass, msgid, div = nil)
97 klass = ClassInfo.normalize_class(klass)
98 key = [Locale.current, klass, msgid, div]
99 msg = @@singular_message_cache[key]
100 return msg if msg and @@cached
101 # Find messages from related classes.
102 each_text_domains(klass) do |text_domain, lang|
103 msg = text_domain.translate_singular_message(lang, msgid)
104 break if msg
105 end
106
107 # If not found, return msgid.
108 msg ||= msgid
109 if div and msg == msgid
110 if index = msg.rindex(div)
111 msg = msg[(index + 1)..-1]
112 end
113 end
114 @@singular_message_cache[key] = msg
115 end
116
117 # This function is similar to the get_singular_message function
118 # as it finds the message catalogs in the same way.
119 # But it takes two extra arguments for plural form.
120 # The msgid parameter must contain the singular form of the string to be converted.
121 # It is also used as the key for the search in the catalog.
122 # The msgid_plural parameter is the plural form.
123 # The parameter n is used to determine the plural form.
124 # If no message catalog is found msgid1 is returned if n == 1, otherwise msgid2.
125 # And if msgid includes "div", it returns a last part of msgid separeted "div".
126 #
127 # * msgid: the singular form with "div". (e.g. "Special|An apple", "An apple")
128 # * msgid_plural: the plural form. (e.g. "%{num} Apples")
129 # * n: a number used to determine the plural form.
130 # * div: the separator. Default is "|".
131 # * Returns: the localized text which key is msgid_plural if n is plural(follow plural-rule) or msgid.
132 # "plural-rule" is defined in po-file.
133 #
134 # or
135 #
136 # * [msgid, msgid_plural] : msgid and msgid_plural an Array
137 # * n: a number used to determine the plural form.
138 # * div: the separator. Default is "|".
139 def translate_plural_message(klass, arg1, arg2, arg3 = "|", arg4 = "|")
140 klass = ClassInfo.normalize_class(klass)
141 # parse arguments
142 if arg1.kind_of?(Array)
143 msgid = arg1[0]
144 msgid_plural = arg1[1]
145 n = arg2
146 if arg3 and arg3.kind_of? Numeric
147 raise ArgumentError, _("ngettext: 3rd parmeter is wrong: value = %{number}") % {:number => arg3}
148 end
149 div = arg3
150 else
151 msgid = arg1
152 msgid_plural = arg2
153 raise ArgumentError, _("ngettext: 3rd parameter should be a number, not nil.") unless arg3
154 n = arg3
155 div = arg4
156 end
157
158 key = [Locale.current, klass, msgid, msgid_plural, div]
159 msgs = @@plural_message_cache[key]
160 unless (msgs and @@cached)
161 # Find messages from related classes.
162 msgs = nil
163 each_text_domains(klass) do |text_domain, lang|
164 msgs = text_domain.translate_plural_message(lang, msgid, msgid_plural)
165 break if msgs
166 end
167
168 msgs = [[msgid, msgid_plural], TextDomain::DEFAULT_PLURAL_CALC] unless msgs
169
170 msgstrs = msgs[0]
171 if div and msgstrs[0] == msgid and index = msgstrs[0].rindex(div)
172 msgstrs[0] = msgstrs[0][(index + 1)..-1]
173 end
174 @@plural_message_cache[key] = msgs
175 end
176
177 # Return the singular or plural message.
178 msgstrs = msgs[0]
179 plural = msgs[1].call(n)
180 return msgstrs[plural] if plural.kind_of?(Numeric)
181 return plural ? msgstrs[1] : msgstrs[0]
182 end
183
184 # for testing.
185 def dump_all_text_domains
186 [
187 @@text_domain_pool.dup,
188 @@text_domain_group_pool.dup,
189 @@gettext_classes.dup,
190 ]
191 end
192
193 # for testing.
194 def restore_all_text_domains(dumped_all_text_domains)
195 @@text_domain_pool, @@text_domain_group_pool, @@gettext_classes =
196 dumped_all_text_domains
197 clear_caches
198 end
199
200 # for testing.
201 def clear_all_text_domains
202 @@text_domain_pool = {}
203 @@text_domain_group_pool = {}
204 @@gettext_classes = []
205 clear_caches
206 end
207
208 # for testing.
209 def clear_caches
210 @@singular_message_cache = {}
211 @@plural_message_cache = {}
212 end
213
214 def create_or_find_text_domain_group(klass) #:nodoc:
215 group = @@text_domain_group_pool[klass]
216 return group if group
217
218 @@text_domain_group_pool[klass] = TextDomainGroup.new
219 end
220
221 def create_or_find_text_domain(name, path, charset)#:nodoc:
222 text_domain = @@text_domain_pool[name]
223 return text_domain if text_domain
224
225 @@text_domain_pool[name] = TextDomain.new(name, path, charset)
226 end
227 end
228 end
+0
-250
vendor/gettext/tools/msgcat.rb less more
0 # Copyright (C) 2014 Kouhei Sutou <kou@clear-code.com>
1 #
2 # License: Ruby's or LGPL
3 #
4 # This library is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Lesser General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 require "optparse"
18 require "gettext"
19 require "gettext/po_parser"
20 require "gettext/po"
21
22 module GetText
23 module Tools
24 class MsgCat
25 class << self
26 # (see #run)
27 #
28 # This method is provided just for convenience. It equals to
29 # `new.run(*command_line)`.
30 def run(*command_line)
31 new.run(*command_line)
32 end
33 end
34
35 # Concatenates po-files.
36 #
37 # @param [Array<String>] command_line
38 # The command line arguments for rmsgcat.
39 # @return [void]
40 def run(*command_line)
41 config = Config.new
42 config.parse(command_line)
43
44 parser = POParser.new
45 parser.report_warning = config.report_warning?
46 parser.ignore_fuzzy = !config.include_fuzzy?
47 output_po = PO.new
48 output_po.order = config.order
49 merger = Merger.new(output_po, config)
50 config.pos.each do |po_file_name|
51 po = PO.new
52 parser.parse_file(po_file_name, po)
53 merger.merge(po)
54 end
55
56 output_po_string = output_po.to_s(config.po_format_options)
57 if config.output.is_a?(String)
58 File.open(File.expand_path(config.output), "w") do |file|
59 file.print(output_po_string)
60 end
61 else
62 puts(output_po_string)
63 end
64 end
65
66 # @private
67 class Merger
68 def initialize(output_po, config)
69 @output_po = output_po
70 @config = config
71 end
72
73 def merge(po)
74 po.each do |entry|
75 id = [entry.msgctxt, entry.msgid]
76 if @output_po.has_key?(*id)
77 merged_entry = merge_entry(@output_po[*id], entry)
78 else
79 merged_entry = entry
80 end
81 @output_po[*id] = merged_entry if merged_entry
82 end
83 end
84
85 private
86 def merge_entry(base_entry, new_entry)
87 if base_entry.header?
88 return merge_header(base_entry, new_entry)
89 end
90
91 if base_entry.fuzzy?
92 return merge_fuzzy_entry(base_entry, new_entry)
93 end
94
95 if base_entry.translated?
96 base_entry
97 else
98 new_entry
99 end
100 end
101
102 def merge_header(base_entry, new_entry)
103 base_entry
104 end
105
106 def merge_fuzzy_entry(base_entry, new_entry)
107 if new_entry.fuzzy?
108 base_entry
109 else
110 new_entry
111 end
112 end
113 end
114
115 # @private
116 class Config
117 include GetText
118
119 bindtextdomain("gettext")
120
121 # @return [Array<String>] The input PO file names.
122 attr_accessor :pos
123
124 # @return [String] The output file name.
125 attr_accessor :output
126
127 # @return [:reference, :msgid] The sort key.
128 attr_accessor :order
129
130 # @return [Hash] The PO format options.
131 # @see PO#to_s
132 # @see POEntry#to_s
133 attr_accessor :po_format_options
134
135 # (see include_fuzzy?)
136 attr_writer :include_fuzzy
137
138 # (see report_warning?)
139 attr_writer :report_warning
140
141 def initialize
142 @pos = []
143 @output = nil
144 @order = nil
145 @po_format_options = {
146 :max_line_width => POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH,
147 }
148 @include_fuzzy = true
149 @report_warning = true
150 end
151
152 # @return [Boolean] Whether includes fuzzy entries or not.
153 def include_fuzzy?
154 @include_fuzzy
155 end
156
157 # @return [Boolean] Whether reports warning messages or not.
158 def report_warning?
159 @report_warning
160 end
161
162 def parse(command_line)
163 parser = create_option_parser
164 @pos = parser.parse(command_line)
165 end
166
167 private
168 def create_option_parser
169 parser = OptionParser.new
170 parser.version = GetText::VERSION
171 parser.banner = _("Usage: %s [OPTIONS] PO_FILE1 PO_FILE2 ...") % $0
172 parser.separator("")
173 parser.separator(_("Concatenates and merges PO files."))
174 parser.separator("")
175 parser.separator(_("Specific options:"))
176
177 parser.on("-o", "--output=FILE",
178 _("Write output to specified file"),
179 _("(default: the standard output)")) do |output|
180 @output = output
181 end
182
183 parser.on("--sort-by-msgid",
184 _("Sort output by msgid")) do
185 @order = :msgid
186 end
187
188 parser.on("--sort-by-location",
189 _("Sort output by location")) do
190 @order = :reference
191 end
192
193 parser.on("--sort-by-file",
194 _("Sort output by location"),
195 _("It is same as --sort-by-location"),
196 _("Just for GNU gettext's msgcat compatibility")) do
197 @order = :reference
198 end
199
200 parser.on("--[no-]sort-output",
201 _("Sort output by msgid"),
202 _("It is same as --sort-by-msgid"),
203 _("Just for GNU gettext's msgcat compatibility")) do |sort|
204 @order = sort ? :msgid : nil
205 end
206
207 parser.on("--no-location",
208 _("Remove location information")) do |boolean|
209 @po_format_options[:include_reference_comment] = boolean
210 end
211
212 parser.on("--no-all-comments",
213 _("Remove all comments")) do |boolean|
214 @po_format_options[:include_all_comments] = boolean
215 end
216
217 parser.on("--width=WIDTH", Integer,
218 _("Set output page width"),
219 "(#{@po_format_options[:max_line_width]})") do |width|
220 @po_format_options[:max_line_width] = width
221 end
222
223 parser.on("--[no-]wrap",
224 _("Break long message lines, longer than the output page width, into several lines"),
225 "(#{@po_format_options[:max_line_width] >= 0})") do |wrap|
226 if wrap
227 max_line_width = POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH
228 else
229 max_line_width = -1
230 end
231 @po_format_options[:max_line_width] = max_line_width
232 end
233
234 parser.on("--no-fuzzy",
235 _("Ignore fuzzy entries")) do |include_fuzzy|
236 @include_fuzzy = include_fuzzy
237 end
238
239 parser.on("--no-report-warning",
240 _("Don't report warning messages")) do |report_warning|
241 @report_warning = report_warning
242 end
243
244 parser
245 end
246 end
247 end
248 end
249 end
+0
-104
vendor/gettext/tools/msgfmt.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
4 # Copyright (C) 2003-2009 Masao Mutoh
5 #
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "optparse"
22 require "fileutils"
23 require "gettext"
24 require "gettext/po_parser"
25
26 module GetText
27 module Tools
28 class MsgFmt #:nodoc:
29 # Create a mo-file from a target file(po-file).
30 # You must specify a path of a target file in arguments.
31 # If a path of a mo-file is not specified in arguments, a mo-file is
32 # created as "messages.mo" in the current directory.
33 # @param [Array<String>] arguments arguments for rmsgfmt.
34 # @return [void]
35 class << self
36 def run(*arguments)
37 new.run(*arguments)
38 end
39 end
40
41 include GetText
42
43 bindtextdomain("gettext")
44
45 def initialize
46 @input_file = nil
47 @output_file = nil
48 end
49
50 def run(*options) # :nodoc:
51 initialize_arguments(*options)
52
53 parser = POParser.new
54 data = MO.new
55
56 parser.parse_file(@input_file, data)
57 data.save_to_file(@output_file)
58 end
59
60 def initialize_arguments(*options) # :nodoc:
61 input_file, output_file = parse_commandline_options(*options)
62
63 if input_file.nil?
64 raise(ArgumentError, _("no input files specified."))
65 end
66
67 if output_file.nil?
68 output_file = "messages.mo"
69 end
70
71 @input_file = input_file
72 @output_file = output_file
73 end
74
75 def parse_commandline_options(*options)
76 output_file = nil
77
78 parser = OptionParser.new
79 parser.banner = _("Usage: %s input.po [-o output.mo]" % $0)
80 parser.separator("")
81 description = _("Generate binary message catalog from textual " +
82 "translation description.")
83 parser.separator(description)
84 parser.separator("")
85 parser.separator(_("Specific options:"))
86
87 parser.on("-o", "--output=FILE",
88 _("write output to specified file")) do |out|
89 output_file = out
90 end
91
92 parser.on_tail("--version", _("display version information and exit")) do
93 puts(VERSION)
94 exit(true)
95 end
96 parser.parse!(options)
97
98 input_file = options[0]
99 [input_file, output_file]
100 end
101 end
102 end
103 end
+0
-411
vendor/gettext/tools/msginit.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
3 # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
4 #
5 # License: Ruby's or LGPL
6 #
7 # This library is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Lesser General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Lesser General Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 require "etc"
21 require "gettext"
22 require "gettext/po_parser"
23 require "gettext/tools/msgmerge"
24 require "locale/info"
25 require "optparse"
26
27 module GetText
28 module Tools
29 class MsgInit
30 class Error < StandardError
31 end
32
33 class ArgumentError < Error
34 end
35
36 class ValidationError < Error
37 end
38
39 class << self
40 # Create a new .po file from initializing .pot file with user's
41 # environment and input.
42 # @param [Array<String>] arguments arguments for rmsginit.
43 # @return [void]
44 def run(*arguments)
45 new.run(*arguments)
46 end
47 end
48
49 include GetText
50
51 bindtextdomain("gettext")
52
53 def initialize
54 @input_file = nil
55 @output_file = nil
56 @locale = nil
57 @language = nil
58 @entry = nil
59 @comment = nil
60 @translator = nil
61 @set_translator = true
62 @translator_name = nil
63 @translator_eamil = nil
64 end
65
66 # Create .po file from .pot file, user's inputs and metadata.
67 # @param [Array] arguments the list of arguments for rmsginit
68 def run(*arguments)
69 parse_arguments(*arguments)
70 validate
71
72 parser = POParser.new
73 parser.ignore_fuzzy = false
74 pot = parser.parse_file(@input_file, GetText::PO.new)
75 po = replace_pot_header(pot)
76
77 File.open(@output_file, "w") do |f|
78 f.puts(po.to_s)
79 end
80 end
81
82 private
83 VERSION = GetText::VERSION
84
85 def parse_arguments(*arguments)
86 parser = OptionParser.new
87 description = _("Create a new .po file from initializing .pot " +
88 "file with user's environment and input.")
89 parser.separator(description)
90 parser.separator("")
91 parser.separator(_("Specific options:"))
92
93 input_description = _("Use INPUT as a .pot file. If INPUT is not " +
94 "specified, INPUT is a .pot file existing " +
95 "the current directory.")
96 parser.on("-i", "--input=FILE", input_description) do |input|
97 @input_file = input
98 end
99
100 output_description = _("Use OUTPUT as a created .po file. If OUTPUT " +
101 "is not specified, OUTPUT depend on LOCALE " +
102 "or the current locale on your environment.")
103 parser.on("-o", "--output=OUTPUT", output_description) do |output|
104 @output_file = output
105 end
106
107 locale_description = _("Use LOCALE as target locale. If LOCALE is " +
108 "not specified, LOCALE is the current " +
109 "locale on your environment.")
110 parser.on("-l", "--locale=LOCALE", locale_description) do |loc|
111 @locale = loc
112 end
113
114 parser.on("--no-translator",
115 _("Don't set translator information")) do
116 @set_translator = false
117 end
118
119 parser.on("--translator-name=NAME",
120 _("Use NAME as translator name")) do |name|
121 @translator_name = name
122 end
123
124 parser.on("--translator-email=EMAIL",
125 _("Use EMAIL as translator email address")) do |email|
126 @translator_email = email
127 end
128
129 parser.on("-h", "--help", _("Display this help and exit")) do
130 puts(parser.help)
131 exit(true)
132 end
133
134 version_description = _("Display version and exit")
135 parser.on_tail("-v", "--version", version_description) do
136 puts(VERSION)
137 exit(true)
138 end
139
140 begin
141 parser.parse!(arguments)
142 rescue OptionParser::ParseError
143 raise(ArgumentError, $!.message)
144 end
145 end
146
147 def validate
148 if @input_file.nil?
149 @input_file = Dir.glob("./*.pot").first
150 if @input_file.nil?
151 raise(ValidationError,
152 _(".pot file does not exist in the current directory."))
153 end
154 else
155 unless File.exist?(@input_file)
156 raise(ValidationError,
157 _("file '%s' does not exist." % @input_file))
158 end
159 end
160
161 if @locale.nil?
162 language_tag = Locale.current
163 else
164 language_tag = Locale::Tag.parse(@locale)
165 end
166
167 unless valid_locale?(language_tag)
168 raise(ValidationError,
169 _("Locale '#{language_tag}' is invalid. " +
170 "Please check if your specified locale is usable."))
171 end
172 @locale = language_tag.to_simple.to_s
173 @language = language_tag.language
174
175 @output_file ||= "#{@locale}.po"
176 if File.exist?(@output_file)
177 raise(ValidationError,
178 _("file '%s' has already existed." % @output_file))
179 end
180 end
181
182 def valid_locale?(language_tag)
183 return false if language_tag.instance_of?(Locale::Tag::Irregular)
184
185 Locale::Info.language_code?(language_tag.language)
186 end
187
188 def replace_pot_header(pot)
189 @entry = pot[""].msgstr
190 @comment = pot[""].translator_comment
191 @translator = translator_info
192
193 replace_entry
194 replace_comment
195
196 pot[""] = @entry
197 pot[""].translator_comment = @comment
198 pot[""].flags = pot[""].flags.reject do |flag|
199 flag == "fuzzy"
200 end
201 pot
202 end
203
204 def translator_info
205 return nil unless @set_translator
206 name = translator_name
207 email = translator_email
208 if name and email
209 "#{name} <#{email}>"
210 else
211 nil
212 end
213 end
214
215 def translator_name
216 @translator_name ||= read_translator_name
217 end
218
219 def read_translator_name
220 prompt(_("Please enter your full name"), guess_translator_name)
221 end
222
223 def guess_translator_name
224 name = guess_translator_name_from_password_entry
225 name ||= ENV["USERNAME"]
226 name
227 end
228
229 def guess_translator_name_from_password_entry
230 password_entry = find_password_entry
231 return nil if password_entry.nil?
232
233 name = password_entry.gecos.split(/,/).first.strip
234 name = nil if name.empty?
235 name
236 end
237
238 def find_password_entry
239 Etc.getpwuid
240 rescue ArgumentError
241 nil
242 end
243
244 def translator_email
245 @translator_email ||= read_translator_email
246 end
247
248 def read_translator_email
249 prompt(_("Please enter your email address"), guess_translator_email)
250 end
251
252 def guess_translator_email
253 ENV["EMAIL"]
254 end
255
256 def prompt(message, default)
257 print(message)
258 print(" [#{default}]") if default
259 print(": ")
260
261 user_input = $stdin.gets.chomp
262 if user_input.empty?
263 default
264 else
265 user_input
266 end
267 end
268
269 def replace_entry
270 replace_last_translator
271 replace_pot_revision_date
272 replace_language
273 replace_plural_forms
274 end
275
276 def replace_comment
277 replace_description
278 replace_first_author
279 replace_copyright_year
280 @comment = @comment.gsub(/^fuzzy$/, "")
281 end
282
283 EMAIL = "EMAIL@ADDRESS"
284 FIRST_AUTHOR_KEY = /^FIRST AUTHOR <#{EMAIL}>, (\d+\.)$/
285
286 def replace_last_translator
287 unless @translator.nil?
288 @entry = @entry.gsub(LAST_TRANSLATOR_KEY, "\\1 #{@translator}")
289 end
290 end
291
292 POT_REVISION_DATE_KEY = /^(PO-Revision-Date:).+/
293
294 def replace_pot_revision_date
295 @entry = @entry.gsub(POT_REVISION_DATE_KEY, "\\1 #{revision_date}")
296 end
297
298 LANGUAGE_KEY = /^(Language:).+/
299 LANGUAGE_TEAM_KEY = /^(Language-Team:).+/
300
301 def replace_language
302 language_name = Locale::Info.get_language(@language).name
303 @entry = @entry.gsub(LANGUAGE_KEY, "\\1 #{@locale}")
304 @entry = @entry.gsub(LANGUAGE_TEAM_KEY, "\\1 #{language_name}")
305 end
306
307 PLURAL_FORMS =
308 /^(Plural-Forms:) nplurals=INTEGER; plural=EXPRESSION;$/
309
310 def replace_plural_forms
311 plural_entry = plural_forms(@language)
312 if PLURAL_FORMS =~ @entry
313 @entry = @entry.gsub(PLURAL_FORMS, "\\1 #{plural_entry}\n")
314 else
315 @entry << "Plural-Forms: #{plural_entry}\n"
316 end
317 end
318
319 def plural_forms(language)
320 case language
321 when "ja", "vi", "ko", /\Azh.*\z/
322 nplural = "1"
323 plural_expression = "0"
324 when "en", "de", "nl", "sv", "da", "no", "fo", "es", "pt",
325 "it", "bg", "el", "fi", "et", "he", "eo", "hu", "tr",
326 "ca", "nb"
327 nplural = "2"
328 plural_expression = "n != 1"
329 when "pt_BR", "fr"
330 nplural = "2"
331 plural_expression = "n>1"
332 when "lv"
333 nplural = "3"
334 plural_expression = "n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2"
335 when "ga"
336 nplural = "3"
337 plural_expression = "n==1 ? 0 : n==2 ? 1 : 2"
338 when "ro"
339 nplural = "3"
340 plural_expression = "n==1 ? 0 : " +
341 "(n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2"
342 when "lt", "bs"
343 nplural = "3"
344 plural_expression = "n%10==1 && n%100!=11 ? 0 : " +
345 "n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2"
346 when "ru", "uk", "sr", "hr"
347 nplural = "3"
348 plural_expression = "n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +
349 "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2"
350 when "cs", "sk"
351 nplural = "3"
352 plural_expression = "(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2"
353 when "pl"
354 nplural = "3"
355 plural_expression = "n==1 ? 0 : n%10>=2 && " +
356 "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2"
357 when "sl"
358 nplural = "4"
359 plural_expression = "n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 " +
360 "|| n%100==4 ? 2 : 3"
361 else
362 nplural = nil
363 plural_expression = nil
364 end
365
366 "nplurals=#{nplural}; plural=#{plural_expression};"
367 end
368
369 DESCRIPTION_TITLE = /^SOME DESCRIPTIVE TITLE\.$/
370
371 def replace_description
372 language_name = Locale::Info.get_language(@language).name
373 package_name = ""
374 @entry.gsub(/Project-Id-Version: (.+?) .+/) do
375 package_name = $1
376 end
377 description = "#{language_name} translations " +
378 "for #{package_name} package."
379 @comment = @comment.gsub(DESCRIPTION_TITLE, "\\1 #{description}")
380 end
381
382 YEAR_KEY = /^(FIRST AUTHOR <#{EMAIL}>,) YEAR\.$/
383 LAST_TRANSLATOR_KEY = /^(Last-Translator:) FULL NAME <#{EMAIL}>$/
384
385 def replace_first_author
386 @comment = @comment.gsub(YEAR_KEY, "\\1 #{year}.")
387 unless @translator.nil?
388 @comment = @comment.gsub(FIRST_AUTHOR_KEY, "#{@translator}, \\1")
389 end
390 end
391
392 COPYRIGHT_KEY = /^(Copyright \(C\)) YEAR (THE PACKAGE'S COPYRIGHT HOLDER)$/
393 def replace_copyright_year
394 @comment = @comment.gsub(COPYRIGHT_KEY, "\\1 #{year} \\2")
395 end
396
397 def now
398 @now ||= Time.now
399 end
400
401 def revision_date
402 now.strftime("%Y-%m-%d %H:%M%z")
403 end
404
405 def year
406 now.year
407 end
408 end
409 end
410 end
+0
-419
vendor/gettext/tools/msgmerge.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2013 Haruka Yoshihara <yoshihara@clear-code.com>
3 # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
4 # Copyright (C) 2005-2009 Masao Mutoh
5 # Copyright (C) 2005,2006 speakillof
6 #
7 # License: Ruby's or LGPL
8 #
9 # This library is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU Lesser General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 require "optparse"
23 require "text"
24 require "gettext"
25 require "gettext/po_parser"
26 require "gettext/po"
27
28 module GetText
29 module Tools
30 class MsgMerge
31 class << self
32 # (see #run)
33 #
34 # This method is provided just for convenience. It equals to
35 # `new.run(*command_line)`.
36 def run(*command_line)
37 new.run(*command_line)
38 end
39 end
40
41 # Merge a po-file inluding translated messages and a new pot-file.
42 #
43 # @param [Array<String>] command_line
44 # command line arguments for rmsgmerge.
45 # @return [void]
46 def run(*command_line)
47 config = Config.new
48 config.parse(command_line)
49
50 parser = POParser.new
51 parser.ignore_fuzzy = false
52 definition_po = PO.new
53 reference_pot = PO.new
54 parser.parse_file(config.definition_po, definition_po)
55 parser.parse_file(config.reference_pot, reference_pot)
56
57 merger = Merger.new(reference_pot, definition_po, config)
58 result = merger.merge
59 result.order = config.order
60 p result if $DEBUG
61 print result.generate_po if $DEBUG
62
63 if config.output.is_a?(String)
64 File.open(File.expand_path(config.output), "w+") do |file|
65 file.write(result.to_s(config.po_format_options))
66 end
67 else
68 puts(result.to_s(config.po_format_options))
69 end
70 end
71
72 # @private
73 class Merger
74 # Merge the reference with the definition: take the #. and
75 # #: comments from the reference, take the # comments from
76 # the definition, take the msgstr from the definition. Add
77 # this merged entry to the output message list.
78
79 POT_DATE_EXTRACT_RE = /POT-Creation-Date:\s*(.*)?\s*$/
80 POT_DATE_RE = /POT-Creation-Date:.*?$/
81
82 def initialize(reference, definition, config)
83 @reference = reference
84 @definition = definition
85 @translated_entries = @definition.reject do |entry|
86 entry.msgstr.nil?
87 end
88 @config = config
89 end
90
91 def merge
92 result = GetText::PO.new
93
94 @reference.each do |entry|
95 id = [entry.msgctxt, entry.msgid]
96 result[*id] = merge_definition(entry)
97 end
98
99 add_obsolete_entry(result) if @config.output_obsolete_entries?
100 result
101 end
102
103 private
104 def merge_definition(entry)
105 msgid = entry.msgid
106 msgctxt = entry.msgctxt
107 id = [msgctxt, msgid]
108
109 if @definition.has_key?(*id)
110 return merge_entry(entry, @definition[*id])
111 end
112
113 return entry unless @config.enable_fuzzy_matching?
114
115 if msgctxt.nil?
116 same_msgid_entry = find_by_msgid(@translated_entries, msgid)
117 if same_msgid_entry and same_msgid_entry.msgctxt
118 return merge_fuzzy_entry(entry, same_msgid_entry)
119 end
120 end
121
122 fuzzy_entry = find_fuzzy_entry(@translated_entries, msgid, msgctxt)
123 if fuzzy_entry
124 return merge_fuzzy_entry(entry, fuzzy_entry)
125 end
126
127 entry
128 end
129
130 def merge_entry(reference_entry, definition_entry)
131 if definition_entry.header?
132 return merge_header(reference_entry, definition_entry)
133 end
134
135 return definition_entry if definition_entry.fuzzy?
136
137 entry = reference_entry
138 entry.translator_comment = definition_entry.translator_comment
139 entry.previous = nil
140
141 unless definition_entry.msgid_plural == reference_entry.msgid_plural
142 entry.flags << "fuzzy"
143 end
144
145 entry.msgstr = definition_entry.msgstr
146 entry
147 end
148
149 def merge_header(new_header, old_header)
150 header = old_header
151 if POT_DATE_EXTRACT_RE =~ new_header.msgstr
152 create_date = $1
153 pot_creation_date = "POT-Creation-Date: #{create_date}"
154 header.msgstr = header.msgstr.gsub(POT_DATE_RE, pot_creation_date)
155 end
156 header.flags = []
157 header
158 end
159
160 def find_by_msgid(entries, msgid)
161 same_msgid_entries = entries.find_all do |entry|
162 entry.msgid == msgid
163 end
164 same_msgid_entries = same_msgid_entries.sort_by do |entry|
165 entry.msgctxt
166 end
167 same_msgid_entries.first
168 end
169
170 def merge_fuzzy_entry(entry, fuzzy_entry)
171 merged_entry = merge_entry(entry, fuzzy_entry)
172 merged_entry.flags << "fuzzy"
173 merged_entry
174 end
175
176 MAX_FUZZY_DISTANCE = 0.5 # XXX: make sure that its value is proper.
177
178 def find_fuzzy_entry(definition, msgid, msgctxt)
179 return nil if msgid == :last
180 min_distance_entry = nil
181 min_distance = MAX_FUZZY_DISTANCE
182
183 same_msgctxt_entries = definition.find_all do |entry|
184 entry.msgctxt == msgctxt and not entry.msgid == :last
185 end
186 same_msgctxt_entries.each do |entry|
187 distance = normalize_distance(entry.msgid, msgid)
188 next if distance.nil?
189 if min_distance > distance
190 min_distance = distance
191 min_distance_entry = entry
192 end
193 end
194
195 min_distance_entry
196 end
197
198 MAX_N_CHARACTERS_DIFFERENCE = 10
199 def normalize_distance(source, destination)
200 n_characters_difference = (source.size - destination.size).abs
201 return nil if n_characters_difference > MAX_N_CHARACTERS_DIFFERENCE
202
203 max_size = [source.size, destination.size].max
204 return 0.0 if max_size.zero?
205
206 Text::Levenshtein.distance(source, destination) / max_size.to_f
207 end
208
209 def add_obsolete_entry(result)
210 obsolete_entry = generate_obsolete_entry(result)
211 return if obsolete_entry.nil?
212
213 result[:last] = obsolete_entry
214 end
215
216 def generate_obsolete_entry(result)
217 obsolete_entries = extract_obsolete_entries(result)
218 obsolete_comments = obsolete_entries.collect do |entry|
219 entry.to_s
220 end
221
222 return nil if obsolete_comments.empty?
223
224 obsolete_entry = POEntry.new(:normal)
225 obsolete_entry.msgid = :last
226 obsolete_entry.comment = obsolete_comments.join("\n")
227 obsolete_entry
228 end
229
230 def extract_obsolete_entries(result)
231 @definition.find_all do |entry|
232 if entry.obsolete?
233 true
234 elsif entry.msgstr.nil?
235 false
236 else
237 id = [entry.msgctxt, entry.msgid]
238 not result.has_key?(*id)
239 end
240 end
241 end
242 end
243
244 # @private
245 class Config
246 include GetText
247
248 bindtextdomain("gettext")
249
250 attr_accessor :definition_po, :reference_pot
251 attr_accessor :output, :update
252 attr_accessor :order
253 attr_accessor :po_format_options
254
255 # update mode options
256 attr_accessor :backup, :suffix
257
258 # (#see #enable_fuzzy_matching?)
259 attr_writer :enable_fuzzy_matching
260
261 # (#see #output_obsolete_entries?)
262 attr_writer :output_obsolete_entries
263
264 # The result is written back to def.po.
265 # --backup=CONTROL make a backup of def.po
266 # --suffix=SUFFIX override the usual backup suffix
267 # The version control method may be selected
268 # via the --backup option or through
269 # the VERSION_CONTROL environment variable. Here are the values:
270 # none, off never make backups (even if --backup is given)
271 # numbered, t make numbered backups
272 # existing, nil numbered if numbered backups exist, simple otherwise
273 # simple, never always make simple backups
274 # The backup suffix is `~', unless set with --suffix or
275 # the SIMPLE_BACKUP_SUFFIX environment variable.
276
277 def initialize
278 @definition_po = nil
279 @reference_po = nil
280 @update = false
281 @output = nil
282 @order = :reference
283 @po_format_options = {
284 :max_line_width => POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH,
285 }
286 @enable_fuzzy_matching = true
287 @update = nil
288 @output_obsolete_entries = true
289 @backup = ENV["VERSION_CONTROL"]
290 @suffix = ENV["SIMPLE_BACKUP_SUFFIX"] || "~"
291 @input_dirs = ["."]
292 end
293
294 def parse(command_line)
295 parser = create_option_parser
296 rest = parser.parse(command_line)
297
298 if rest.size != 2
299 puts(parser.help)
300 exit(false)
301 end
302
303 @definition_po, @reference_pot = rest
304 @output = @definition_po if @update
305 end
306
307 # @return [Bool] true if fuzzy matching is enabled, false otherwise.
308 def enable_fuzzy_matching?
309 @enable_fuzzy_matching
310 end
311
312 # @return [Bool] true if outputting obsolete entries is
313 # enabled, false otherwise.
314 def output_obsolete_entries?
315 @output_obsolete_entries
316 end
317
318 private
319 def create_option_parser
320 parser = OptionParser.new
321 parser.banner =
322 _("Usage: %s [OPTIONS] definition.po reference.pot") % $0
323 #parser.summary_width = 80
324 parser.separator("")
325 description = _("Merges two Uniforum style .po files together. " +
326 "The definition.po file is an existing PO file " +
327 "with translations. The reference.pot file is " +
328 "the last created PO file with up-to-date source " +
329 "references. The reference.pot is generally " +
330 "created by rxgettext.")
331 parser.separator(description)
332 parser.separator("")
333 parser.separator(_("Specific options:"))
334
335 parser.on("-U", "--[no-]update",
336 _("Update definition.po")) do |update|
337 @update = update
338 end
339
340 parser.on("-o", "--output=FILE",
341 _("Write output to specified file")) do |output|
342 @output = output
343 end
344
345 parser.on("--[no-]sort-output",
346 _("Sort output by msgid"),
347 _("It is same as --sort-by-msgid"),
348 _("Just for GNU gettext's msgcat compatibility")) do |sort|
349 @order = sort ? :msgid : nil
350 end
351
352 parser.on("--sort-by-file",
353 _("Sort output by location"),
354 _("It is same as --sort-by-location"),
355 _("Just for GNU gettext's msgcat compatibility")) do
356 @order = :reference
357 end
358
359 parser.on("--sort-by-location",
360 _("Sort output by location")) do
361 @order = :reference
362 end
363
364 parser.on("--sort-by-msgid",
365 _("Sort output by msgid")) do
366 @order = :msgid
367 end
368
369 parser.on("--[no-]location",
370 _("Preserve '#: FILENAME:LINE' lines")) do |location|
371 @po_format_options[:include_reference_comment] = location
372 end
373
374 parser.on("--width=WIDTH", Integer,
375 _("Set output page width"),
376 "(#{@po_format_options[:max_line_width]})") do |width|
377 @po_format_options[:max_line_width] = width
378 end
379
380 parser.on("--[no-]wrap",
381 _("Break long message lines, longer than the output page width, into several lines"),
382 "(#{@po_format_options[:max_line_width] >= 0})") do |wrap|
383 if wrap
384 max_line_width = POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH
385 else
386 max_line_width = -1
387 end
388 @po_format_options[:max_line_width] = max_line_width
389 end
390
391 parser.on("--[no-]fuzzy-matching",
392 _("Disable fuzzy matching"),
393 _("(enable)")) do |boolean|
394 @enable_fuzzy_matching = boolean
395 end
396
397 parser.on("--no-obsolete-entries",
398 _("Don't output obsolete entries")) do |boolean|
399 @output_obsolete_entries = boolean
400 end
401
402 parser.on("-h", "--help", _("Display this help and exit")) do
403 puts(parser.help)
404 exit(true)
405 end
406
407 parser.on_tail("--version",
408 _("Display version information and exit")) do
409 puts(GetText::VERSION)
410 exit(true)
411 end
412
413 parser
414 end
415 end
416 end
417 end
418 end
+0
-93
vendor/gettext/tools/parser/erb.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 parser/erb.rb - parser for ERB
4
5 Copyright (C) 2005-2009 Masao Mutoh
6
7 You may redistribute it and/or modify it under the same
8 license terms as Ruby or LGPL.
9 =end
10
11 require 'erb'
12 require 'gettext/tools/parser/ruby'
13
14 module GetText
15 class ErbParser
16 @config = {
17 :extnames => ['.rhtml', '.erb']
18 }
19
20 class << self
21 # Sets some preferences to parse ERB files.
22 # * config: a Hash of the config. It can takes some values below:
23 # * :extnames: An Array of target files extension. Default is [".rhtml"].
24 def init(config)
25 config.each{|k, v|
26 @config[k] = v
27 }
28 end
29
30 def target?(file) # :nodoc:
31 @config[:extnames].each do |v|
32 return true if File.extname(file) == v
33 end
34 false
35 end
36
37 # Parses eRuby script located at `path`.
38 #
39 # This is a short cut method. It equals to `new(path,
40 # options).parse`.
41 #
42 # @return [Array<POEntry>] Extracted messages
43 # @see #initialize and #parse
44 def parse(path, options={})
45 parser = new(path, options)
46 parser.parse
47 end
48 end
49
50 MAGIC_COMMENT = /\A#coding:.*\n/
51
52 # @param path [String] eRuby script path to be parsed
53 # @param options [Hash]
54 def initialize(path, options={})
55 @path = path
56 @options = options
57 end
58
59 # Extracts messages from @path.
60 #
61 # @return [Array<POEntry>] Extracted messages
62 def parse
63 content = IO.read(@path)
64 src = ERB.new(content).src
65
66 # Force the src encoding back to the encoding in magic comment
67 # or original content.
68 encoding = detect_encoding(src) || content.encoding
69 src.force_encoding(encoding)
70
71 # Remove magic comment prepended by erb in Ruby 1.9.
72 src = src.gsub(MAGIC_COMMENT, "")
73
74 RubyParser.new(@path, @options).parse_source(src)
75 end
76
77 def detect_encoding(erb_source)
78 if /\A#coding:(.*)\n/ =~ erb_source
79 $1
80 else
81 nil
82 end
83 end
84 end
85 end
86
87 if __FILE__ == $0
88 # ex) ruby glade.rhtml foo.rhtml bar.rhtml
89 ARGV.each do |file|
90 p GetText::ErbParser.parse(file)
91 end
92 end
+0
-109
vendor/gettext/tools/parser/glade.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 parser/glade.rb - parser for Glade-2
4
5 Copyright (C) 2013 Kouhei Sutou <kou@clear-code.com>
6 Copyright (C) 2004,2005 Masao Mutoh
7
8 You may redistribute it and/or modify it under the same
9 license terms as Ruby or LGPL.
10 =end
11
12 require 'cgi'
13 require 'gettext'
14
15 module GetText
16 class GladeParser
17 extend GetText
18
19 bindtextdomain("gettext")
20
21 class << self
22 XML_RE = /<\?xml/
23 GLADE_RE = /glade-2.0.dtd/
24
25 def target?(file) # :nodoc:
26 data = IO.readlines(file)
27 if XML_RE =~ data[0] and GLADE_RE =~ data[1]
28 true
29 else
30 if File.extname(file) == '.glade'
31 raise _("`%{file}' is not glade-2.0 format.") % {:file => file}
32 end
33 false
34 end
35 end
36
37 def parse(path, options={})
38 parser = new(path, options)
39 parser.parse
40 end
41 end
42
43 TARGET1 = /<property.*translatable="yes">(.*)/
44 TARGET2 = /(.*)<\/property>/
45
46 def initialize(path, options={})
47 @path = path
48 @options = options
49 end
50
51 def parse # :nodoc:
52 File.open(@path) do |file|
53 parse_source(file)
54 end
55 end
56
57 private
58 def parse_source(input) # :nodoc:
59 targets = []
60 target = false
61 start_line_no = nil
62 val = nil
63
64 input.each_line.with_index do |line, i|
65 if TARGET1 =~ line
66 start_line_no = i + 1
67 val = $1 + "\n"
68 target = true
69 if TARGET2 =~ $1
70 val = $1
71 add_target(val, start_line_no, targets)
72 val = nil
73 target = false
74 end
75 elsif target
76 if TARGET2 =~ line
77 val << $1
78 add_target(val, start_line_no, targets)
79 val = nil
80 target = false
81 else
82 val << line
83 end
84 end
85 end
86 targets
87 end
88
89 def add_target(val, line_no, targets) # :nodoc:
90 return unless val.size > 0
91 assoc_data = targets.assoc(val)
92 val = CGI.unescapeHTML(val)
93 if assoc_data
94 targets[targets.index(assoc_data)] = assoc_data << "#{@path}:#{line_no}"
95 else
96 targets << [val.gsub(/\n/, '\n'), "#{@path}:#{line_no}"]
97 end
98 targets
99 end
100 end
101 end
102
103 if __FILE__ == $0
104 # ex) ruby glade.rb foo.glade bar.glade
105 ARGV.each do |file|
106 p GetText::GladeParser.parse(file)
107 end
108 end
+0
-330
vendor/gettext/tools/parser/ruby.rb less more
0 # -*- coding: utf-8 -*-
1 =begin
2 parser/ruby.rb - parser for ruby script
3
4 Copyright (C) 2013 Kouhei Sutou <kou@clear-code.com>
5 Copyright (C) 2003-2009 Masao Mutoh
6 Copyright (C) 2005 speakillof
7 Copyright (C) 2001,2002 Yasushi Shoji, Masao Mutoh
8
9 You may redistribute it and/or modify it under the same
10 license terms as Ruby or LGPL.
11
12 =end
13
14 require "irb/ruby-lex"
15 require "stringio"
16 require "gettext/po_entry"
17
18 module GetText
19 class RubyLexX < RubyLex # :nodoc: all
20 # Parser#parse resemlbes RubyLex#lex
21 def parse
22 until ( (tk = token).kind_of?(RubyToken::TkEND_OF_SCRIPT) && !@continue or tk.nil? )
23 s = get_readed
24 if RubyToken::TkSTRING === tk or RubyToken::TkDSTRING === tk
25 def tk.value
26 @value
27 end
28
29 def tk.value=(s)
30 @value = s
31 end
32
33 if @here_header
34 s = s.sub(/\A.*?\n/, "").sub(/^.*\n\Z/, "")
35 else
36 begin
37 s = eval(s)
38 rescue Exception
39 # Do nothing.
40 end
41 end
42
43 tk.value = s
44 end
45
46 if $DEBUG
47 if tk.is_a? TkSTRING or tk.is_a? TkDSTRING
48 $stderr.puts("#{tk}: #{tk.value}")
49 elsif tk.is_a? TkIDENTIFIER
50 $stderr.puts("#{tk}: #{tk.name}")
51 else
52 $stderr.puts(tk)
53 end
54 end
55
56 yield tk
57 end
58 return nil
59 end
60
61 # Original parser does not keep the content of the comments,
62 # so monkey patching this with new token type and extended
63 # identify_comment implementation
64 RubyToken.def_token :TkCOMMENT_WITH_CONTENT, TkVal
65
66 def identify_comment
67 @ltype = "#"
68 get_readed # skip the hash sign itself
69
70 while ch = getc
71 if ch == "\n"
72 @ltype = nil
73 ungetc
74 break
75 end
76 end
77 return Token(TkCOMMENT_WITH_CONTENT, get_readed)
78 end
79
80 end
81
82 # Extends POEntry for RubyParser.
83 # Implements a sort of state machine to assist the parser.
84 module POEntryForRubyParser
85 # Supports parsing by setting attributes by and by.
86 def set_current_attribute(str)
87 param = @param_type[@param_number]
88 raise ParseError, "no more string parameters expected" unless param
89 set_value(param, str)
90 end
91
92 def init_param
93 @param_number = 0
94 self
95 end
96
97 def advance_to_next_attribute
98 @param_number += 1
99 end
100 end
101 class POEntry
102 include POEntryForRubyParser
103 alias :initialize_old :initialize
104 def initialize(type)
105 initialize_old(type)
106 init_param
107 end
108 end
109
110 class RubyParser
111 ID = ["gettext", "_", "N_", "sgettext", "s_"]
112 PLURAL_ID = ["ngettext", "n_", "Nn_", "ns_", "nsgettext"]
113 MSGCTXT_ID = ["pgettext", "p_"]
114 MSGCTXT_PLURAL_ID = ["npgettext", "np_"]
115
116 class << self
117 def target?(file) # :nodoc:
118 true # always true, as the default parser.
119 end
120
121 # Parses Ruby script located at `path`.
122 #
123 # This is a short cut method. It equals to `new(path,
124 # options).parse`.
125 #
126 # @param (see #initialize)
127 # @option (see #initialize)
128 # @return (see #parse)
129 # @see #initialize
130 # @see #parse
131 def parse(path, options={})
132 parser = new(path, options)
133 parser.parse
134 end
135 end
136
137 #
138 # @example `:comment_tag` option: String tag
139 # path = "hello.rb"
140 # # content:
141 # # # TRANSLATORS: This is a comment to translators.
142 # # _("Hello")
143 # #
144 # # # This is a comment for programmers.
145 # # # TRANSLATORS: This is a comment to translators.
146 # # # This is also a comment to translators.
147 # # _("World")
148 # #
149 # # # This is a comment for programmers.
150 # # # This is also a comment for programmers
151 # # # because all lines don't start with "TRANSRATORS:".
152 # # _("Bye")
153 # options = {:comment_tag => "TRANSLATORS:"}
154 # parser = GetText::RubyParser.new(path, options)
155 # parser.parse
156 # # => [
157 # # POEntry<
158 # # :msgid => "Hello",
159 # # :extracted_comment =>
160 # # "TRANSLATORS: This is a comment to translators.",
161 # # >,
162 # # POEntry<
163 # # :msgid => "World",
164 # # :extracted_comment =>
165 # # "TRANSLATORS: This is a comment to translators.\n" +
166 # # "This is also a comment to translators.",
167 # # >,
168 # # POEntry<
169 # # :msgid => "Bye",
170 # # :extracted_comment => nil,
171 # # >,
172 # # ]
173 #
174 # @example `:comment_tag` option: nil tag
175 # path = "hello.rb"
176 # # content:
177 # # # This is a comment to translators.
178 # # # This is also a comment for translators.
179 # # _("Hello")
180 # options = {:comment_tag => nil}
181 # parser = GetText::RubyParser.new(path, options)
182 # parser.parse
183 # # => [
184 # # POEntry<
185 # # :msgid => "Hello",
186 # # :extracted_comment =>
187 # # "This is a comment to translators.\n" +
188 # # " This is also a comment for translators.",
189 # # >,
190 # # ]
191 #
192 # @param path [String] Ruby script path to be parsed
193 # @param options [Hash] Options
194 # @option options [String, nil] :comment_tag The tag to
195 # detect comments to be extracted. The extracted comments are
196 # used to deliver messages to translators from programmers.
197 #
198 # If the tag is String and a line in a comment start with the
199 # tag, the line and the following lines are extracted.
200 #
201 # If the tag is nil, all comments are extracted.
202 def initialize(path, options={})
203 @path = path
204 @options = options
205 end
206
207 # Extracts messages from @path.
208 #
209 # @return [Array<POEntry>] Extracted messages
210 def parse
211 source = IO.read(@path)
212
213 encoding = detect_encoding(source) || source.encoding
214 source.force_encoding(encoding)
215
216 parse_source(source)
217 end
218
219 def detect_encoding(source)
220 binary_source = source.dup.force_encoding("ASCII-8BIT")
221 if /\A.*coding\s*[=:]\s*([[:alnum:]\-_]+)/ =~ binary_source
222 $1.gsub(/-(?:unix|mac|dos)\z/, "")
223 else
224 nil
225 end
226 end
227
228 def parse_source(source)
229 po = []
230 file = StringIO.new(source)
231 rl = RubyLexX.new
232 rl.set_input(file)
233 rl.skip_space = true
234 #rl.readed_auto_clean_up = true
235
236 po_entry = nil
237 line_no = nil
238 last_comment = ""
239 reset_comment = false
240 ignore_next_comma = false
241 rl.parse do |tk|
242 begin
243 ignore_current_comma = ignore_next_comma
244 ignore_next_comma = false
245 case tk
246 when RubyToken::TkIDENTIFIER, RubyToken::TkCONSTANT
247 if store_po_entry(po, po_entry, line_no, last_comment)
248 last_comment = ""
249 end
250 if ID.include?(tk.name)
251 po_entry = POEntry.new(:normal)
252 elsif PLURAL_ID.include?(tk.name)
253 po_entry = POEntry.new(:plural)
254 elsif MSGCTXT_ID.include?(tk.name)
255 po_entry = POEntry.new(:msgctxt)
256 elsif MSGCTXT_PLURAL_ID.include?(tk.name)
257 po_entry = POEntry.new(:msgctxt_plural)
258 else
259 po_entry = nil
260 end
261 line_no = tk.line_no.to_s
262 when RubyToken::TkSTRING, RubyToken::TkDSTRING
263 po_entry.set_current_attribute tk.value if po_entry
264 when RubyToken::TkPLUS, RubyToken::TkNL
265 #do nothing
266 when RubyToken::TkINTEGER
267 ignore_next_comma = true
268 when RubyToken::TkCOMMA
269 unless ignore_current_comma
270 po_entry.advance_to_next_attribute if po_entry
271 end
272 else
273 if store_po_entry(po, po_entry, line_no, last_comment)
274 po_entry = nil
275 last_comment = ""
276 end
277 end
278 rescue
279 $stderr.print "\n\nError"
280 $stderr.print " parsing #{@path}:#{tk.line_no}\n\t #{source.lines.to_a[tk.line_no - 1]}" if tk
281 $stderr.print "\n #{$!.inspect} in\n"
282 $stderr.print $!.backtrace.join("\n")
283 $stderr.print "\n"
284 exit 1
285 end
286
287 case tk
288 when RubyToken::TkCOMMENT_WITH_CONTENT
289 last_comment = "" if reset_comment
290 if last_comment.empty?
291 comment1 = tk.value.lstrip
292 if comment_to_be_extracted?(comment1)
293 last_comment << comment1
294 end
295 else
296 last_comment += "\n"
297 last_comment += tk.value
298 end
299 reset_comment = false
300 when RubyToken::TkNL
301 else
302 reset_comment = true
303 end
304 end
305 po
306 end
307
308 private
309 def store_po_entry(po, po_entry, line_no, last_comment) #:nodoc:
310 if po_entry && po_entry.msgid
311 po_entry.references << @path + ":" + line_no
312 po_entry.add_comment(last_comment) unless last_comment.empty?
313 po << po_entry
314 true
315 else
316 false
317 end
318 end
319
320 def comment_to_be_extracted?(comment)
321 return false unless @options.has_key?(:comment_tag)
322
323 tag = @options[:comment_tag]
324 return true if tag.nil?
325
326 /\A#{Regexp.escape(tag)}/ === comment
327 end
328 end
329 end
+0
-455
vendor/gettext/tools/task.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
3 #
4 # License: Ruby's or LGPL
5 #
6 # This library is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 require "rake"
20 require "gettext/tools"
21
22 module GetText
23 module Tools
24 class Task
25 class Error < StandardError
26 end
27
28 class ValidationError < Error
29 attr_reader :reasons
30 def initialize(reasons)
31 @reasons = reasons
32 lines = []
33 lines << "invalid configurations:"
34 @reasons.each do |variable, reason|
35 lines << "#{variable}: #{reason}"
36 end
37 super(lines.join("\n"))
38 end
39 end
40
41 include GetText
42 include Rake::DSL
43
44 class << self
45 # Define gettext related Rake tasks. Normally, use this method
46 # to define tasks because this method is a convenient API.
47 #
48 # See accessor APIs how to configure this task.
49 #
50 # See {#define} for what task is defined.
51 #
52 # @example Recommended usage
53 # require "gettext/tools/task"
54 # # Recommended usage
55 # GetText::Tools::Task.define do |task|
56 # task.spec = spec
57 # # ...
58 # end
59 # # Low level API
60 # task = GetText::Tools::Task.new
61 # task.spec = spec
62 # # ...
63 # task.define
64 #
65 # @yield [task] Gives the newely created task to the block.
66 # @yieldparam [GetText::Tools::Task] task The task that should be
67 # configured.
68 # @see {#define}
69 # @return [void]
70 def define
71 task = new
72 yield(task)
73 task.define
74 end
75 end
76
77 # @return [Gem::Specification, nil] Package information associated
78 # with the task.
79 attr_reader :spec
80
81 # @return [String, nil] Package name for messages.
82 attr_accessor :package_name
83
84 # @return [String, nil] Package version for messages.
85 attr_accessor :package_version
86
87 # It is a required parameter.
88 #
89 # @return [Array<String>] Supported locales. It is filled from
90 # {#po_base_directory} by default.
91 attr_accessor :locales
92 attr_accessor :po_base_directory
93 # @return [String] Base directory that has generated MOs. MOs
94 # are generated into
95 # `#{mo_base_directory}/#{locale}/LC_MESSAGES/#{domain}.mo`.
96 attr_accessor :mo_base_directory
97 # It is a required parameter.
98 #
99 # @return [Array<String>] Files that have messages.
100 attr_accessor :files
101 # It is a required parameter.
102 #
103 # @return [String] Text domain
104 attr_accessor :domain
105
106 # It is useful when you have multiple domains. You can define tasks
107 # for each domains by using different namespace prefix.
108 #
109 # It is `nil` by default. It means that tasks are defined at top
110 # level.
111 #
112 # TODO: example
113 #
114 # @return [String] Namespace prefix for tasks defined by this class.
115 attr_accessor :namespace_prefix
116
117 # @return [Array<String>] Command line options for extracting messages
118 # from sources.
119 # @see GetText::Tools::XGetText
120 # @see `rxgettext --help`
121 attr_accessor :xgettext_options
122
123 # @return [Array<String>] Command line options for merging PO with the
124 # latest POT.
125 # @see GetText::Tools::MsgMerge
126 # @see `rmsgmerge --help`
127 attr_accessor :msgmerge_options
128
129 # @return [Bool]
130 # @see #enable_description? For details.
131 attr_writer :enable_description
132
133 # @return [Bool]
134 # @see #enable_po? For details.
135 attr_writer :enable_po
136
137 # @param [Gem::Specification, nil] spec Package information associated
138 # with the task. Some information are extracted from the spec.
139 # @see #spec= What information are extracted from the spec.
140 def initialize(spec=nil)
141 initialize_variables
142 self.spec = spec
143 if spec
144 yield(self) if block_given?
145 warn("Use #{self.class.name}.define instead of #{self.class.name}.new(spec).")
146 define
147 end
148 end
149
150 # Sets package infromation by Gem::Specification. Here is a list
151 # for information extracted from the spec:
152 #
153 # * {#package_name}
154 # * {#package_version}
155 # * {#domain}
156 # * {#files}
157 #
158 # @param [Gem::Specification] spec package information for the
159 # i18n application.
160 def spec=(spec)
161 @spec = spec
162 return if @spec.nil?
163
164 @package_name = spec.name
165 @package_version = spec.version.to_s
166 @domain ||= spec.name
167 @files += target_files
168 end
169
170 # Define tasks from configured parameters.
171 #
172 # TODO: List defined Rake tasks.
173 def define
174 ensure_variables
175 validate
176
177 define_file_tasks
178 if namespace_prefix
179 namespace_recursive namespace_prefix do
180 define_named_tasks
181 end
182 else
183 define_named_tasks
184 end
185 end
186
187 # If it is true, each task has description. Otherwise, all tasks
188 # doesn't have description.
189 #
190 # @return [Bool]
191 # @since 3.0.1
192 def enable_description?
193 @enable_description
194 end
195
196 # If it is true, PO related tasks are defined. Otherwise, they
197 # are not defined.
198 #
199 # This parameter is useful to manage PO written by hand.
200 #
201 # @return [Bool]
202 # @since 3.0.1
203 def enable_po?
204 @enable_po
205 end
206
207 private
208 def initialize_variables
209 @spec = nil
210 @package_name = nil
211 @package_version = nil
212 @locales = []
213 @po_base_directory = "po"
214 @mo_base_directory = "locale"
215 @files = []
216 @domain = nil
217 @namespace_prefix = nil
218 @xgettext_options = []
219 @msgmerge_options = []
220 @enable_description = true
221 @enable_po = true
222 end
223
224 def ensure_variables
225 @locales = detect_locales if @locales.empty?
226 end
227
228 def validate
229 reasons = {}
230 if @locales.empty?
231 reasons["locales"] = "must set one or more locales"
232 end
233 if @enable_po and @files.empty?
234 reasons["files"] = "must set one or more files"
235 end
236 if @domain.nil?
237 reasons["domain"] = "must set domain"
238 end
239 raise ValidationError.new(reasons) unless reasons.empty?
240 end
241
242 def desc(*args)
243 return unless @enable_description
244 super
245 end
246
247 def define_file_tasks
248 define_pot_file_task
249
250 locales.each do |locale|
251 define_po_file_task(locale)
252 define_mo_file_task(locale)
253 end
254 end
255
256 def define_pot_file_task
257 return unless @enable_po
258
259 pot_dependencies = files.dup
260 unless File.exist?(po_base_directory)
261 directory po_base_directory
262 pot_dependencies << po_base_directory
263 end
264 file pot_file => pot_dependencies do
265 command_line = [
266 "--output", pot_file,
267 ]
268 if package_name
269 command_line.concat(["--package-name", package_name])
270 end
271 if package_version
272 command_line.concat(["--package-version", package_version])
273 end
274 command_line.concat(@xgettext_options)
275 command_line.concat(files)
276 GetText::Tools::XGetText.run(*command_line)
277 end
278 end
279
280 def define_po_file_task(locale)
281 return unless @enable_po
282
283 _po_file = po_file(locale)
284 po_dependencies = [pot_file]
285 _po_directory = po_directory(locale)
286 unless File.exist?(_po_directory)
287 directory _po_directory
288 po_dependencies << _po_directory
289 end
290 file _po_file => po_dependencies do
291 if File.exist?(_po_file)
292 command_line = [
293 "--update",
294 ]
295 command_line.concat(@msgmerge_options)
296 command_line.concat([_po_file, pot_file])
297 GetText::Tools::MsgMerge.run(*command_line)
298 else
299 GetText::Tools::MsgInit.run("--input", pot_file,
300 "--output", _po_file,
301 "--locale", locale.to_s)
302 end
303 end
304 end
305
306 def define_mo_file_task(locale)
307 _po_file = po_file(locale)
308 mo_dependencies = [_po_file]
309 _mo_directory = mo_directory(locale)
310 unless File.exist?(_mo_directory)
311 directory _mo_directory
312 mo_dependencies << _mo_directory
313 end
314 _mo_file = mo_file(locale)
315 file _mo_file => mo_dependencies do
316 GetText::Tools::MsgFmt.run(_po_file, "--output", _mo_file)
317 end
318 end
319
320 def define_named_tasks
321 namespace :gettext do
322 if @enable_po
323 define_pot_tasks
324 define_po_tasks
325 end
326
327 define_mo_tasks
328 end
329
330 desc "Update *.mo"
331 task :gettext => (current_scope + ["gettext", "mo", "update"]).join(":")
332 end
333
334 def define_pot_tasks
335 namespace :pot do
336 desc "Create #{pot_file}"
337 task :create => pot_file
338 end
339 end
340
341 def define_po_tasks
342 namespace :po do
343 desc "Add a new locale"
344 task :add, [:locale] do |_task, args|
345 locale = args.locale || ENV["LOCALE"]
346 if locale.nil?
347 raise "specify locale name by " +
348 "'rake #{_task.name}[${LOCALE}]' or " +
349 "rake #{_task.name} LOCALE=${LOCALE}'"
350 end
351 define_po_file_task(locale)
352 Rake::Task[po_file(locale)].invoke
353 end
354
355 update_tasks = []
356 @locales.each do |locale|
357 namespace locale do
358 desc "Update #{po_file(locale)}"
359 task :update => po_file(locale)
360 update_tasks << (current_scope + ["update"]).join(":")
361 end
362 end
363
364 desc "Update *.po"
365 task :update => update_tasks
366 end
367 end
368
369 def define_mo_tasks
370 namespace :mo do
371 update_tasks = []
372 @locales.each do |locale|
373 namespace locale do
374 desc "Update #{mo_file(locale)}"
375 task :update => mo_file(locale)
376 update_tasks << (current_scope + ["update"]).join(":")
377 end
378 end
379
380 desc "Update *.mo"
381 task :update => update_tasks
382 end
383 end
384
385 def pot_file
386 File.join(po_base_directory, "#{domain}.pot")
387 end
388
389 def po_directory(locale)
390 File.join(po_base_directory, locale.to_s)
391 end
392
393 def po_file(locale)
394 File.join(po_directory(locale), "#{domain}.po")
395 end
396
397 def mo_directory(locale)
398 File.join(mo_base_directory, locale.to_s, "LC_MESSAGES")
399 end
400
401 def mo_file(locale)
402 File.join(mo_directory(locale), "#{domain}.mo")
403 end
404
405 def target_files
406 files = @spec.files.find_all do |file|
407 /\A\.(?:rb|erb|glade)\z/i =~ File.extname(file)
408 end
409 files += @spec.executables.collect do |executable|
410 "bin/#{executable}"
411 end
412 files
413 end
414
415 def detect_locales
416 locales = []
417 return locales unless File.exist?(po_base_directory)
418
419 Dir.open(po_base_directory) do |dir|
420 dir.each do |entry|
421 next unless /\A[a-z]{2}(?:_[A-Z]{2})?\z/ =~ entry
422 next unless File.directory?(File.join(dir.path, entry))
423 locales << entry
424 end
425 end
426 locales
427 end
428
429 def current_scope
430 scope = Rake.application.current_scope
431 if scope.is_a?(Array)
432 scope
433 else
434 if scope.empty?
435 []
436 else
437 [scope.path]
438 end
439 end
440 end
441
442 def namespace_recursive(namespace_spec, &block)
443 first, rest = namespace_spec.split(/:/, 2)
444 namespace first do
445 if rest.nil?
446 block.call
447 else
448 namespace_recursive(rest, &block)
449 end
450 end
451 end
452 end
453 end
454 end
+0
-441
vendor/gettext/tools/xgettext.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
3 # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
4 # Copyright (C) 2003-2010 Masao Mutoh
5 # Copyright (C) 2001,2002 Yasushi Shoji, Masao Mutoh
6 #
7 # License: Ruby's or LGPL
8 #
9 # This library is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU Lesser General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 require "pathname"
23 require "optparse"
24 require "locale"
25 require "gettext"
26 require "gettext/po"
27
28 module GetText
29 module Tools
30 class XGetText
31 class << self
32 def run(*arguments)
33 new.run(*arguments)
34 end
35
36 # Adds a parser to the default parser list.
37 #
38 # @param (see #add_parser)
39 # @return [void]
40 #
41 # @see #add_parser
42 def add_parser(parser)
43 @@default_parsers.unshift(parser)
44 end
45 end
46
47 include GetText
48
49 bindtextdomain("gettext")
50
51 # @api private
52 @@default_parsers = []
53 builtin_parser_info_list = [
54 ["glade", "GladeParser"],
55 ["erb", "ErbParser"],
56 # ["ripper", "RipperParser"],
57 ["ruby", "RubyParser"] # Default parser.
58 ]
59 builtin_parser_info_list.each do |f, klass|
60 begin
61 require "gettext/tools/parser/#{f}"
62 @@default_parsers << GetText.const_get(klass)
63 rescue
64 $stderr.puts(_("'%{klass}' is ignored.") % {:klass => klass})
65 $stderr.puts($!) if $DEBUG
66 end
67 end
68
69 # @return [Hash<Symbol, Object>] Options for parsing. Options
70 # are depend on each parser.
71 # @see RubyParser#parse
72 # @see ErbParser#parse
73 attr_reader :parse_options
74
75 def initialize #:nodoc:
76 @parsers = @@default_parsers.dup
77
78 @input_files = nil
79 @output = nil
80
81 @package_name = "PACKAGE"
82 @package_version = "VERSION"
83 @msgid_bugs_address = ""
84 @copyright_holder = "THE PACKAGE'S COPYRIGHT HOLDER"
85 @copyright_year = "YEAR"
86 @output_encoding = "UTF-8"
87
88 @parse_options = {}
89
90 @po_order = :references
91 @po_format_options = {
92 :max_line_width => POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH,
93 }
94 end
95
96 # The parser object requires to have target?(path) and
97 # parse(path) method.
98 #
99 # @example How to add your parser
100 # require "gettext/tools/xgettext"
101 # class FooParser
102 # def target?(path)
103 # File.extname(path) == ".foo" # *.foo file only.
104 # end
105 # def parse(path, options={})
106 # po = []
107 # # Simple entry
108 # entry = POEntry.new(:normal)
109 # entry.msgid = "hello"
110 # entry.references = ["foo.rb:200", "bar.rb:300"]
111 # entry.add_comment("Comment for the entry")
112 # po << entry
113 # # Plural entry
114 # entry = POEntry.new(:plural)
115 # entry.msgid = "An apple"
116 # entry.msgid_plural = "Apples"
117 # entry.references = ["foo.rb:200", "bar.rb:300"]
118 # po << entry
119 # # Simple entry with the entry context
120 # entry = POEntry.new(:msgctxt)
121 # entry.msgctxt = "context"
122 # entry.msgid = "hello"
123 # entry.references = ["foo.rb:200", "bar.rb:300"]
124 # po << entry
125 # # Plural entry with the message context.
126 # entry = POEntry.new(:msgctxt_plural)
127 # entry.msgctxt = "context"
128 # entry.msgid = "An apple"
129 # entry.msgid_plural = "Apples"
130 # entry.references = ["foo.rb:200", "bar.rb:300"]
131 # po << entry
132 # return po
133 # end
134 # end
135 #
136 # GetText::Tools::XGetText.add_parser(FooParser.new)
137 #
138 # @param [#target?, #parse] parser
139 # It parses target file and extracts translate target entries from the
140 # target file. If there are multiple target files, parser.parse is
141 # called multiple times.
142 # @return [void]
143 def add_parser(parser)
144 @parsers.unshift(parser)
145 end
146
147 def run(*options) # :nodoc:
148 check_command_line_options(*options)
149
150 pot = generate_pot(@input_files)
151
152 if @output.is_a?(String)
153 File.open(File.expand_path(@output), "w+") do |file|
154 file.puts(pot)
155 end
156 else
157 @output.puts(pot)
158 end
159 self
160 end
161
162 def parse(paths) # :nodoc:
163 po = PO.new
164 paths = [paths] if paths.kind_of?(String)
165 paths.each do |path|
166 begin
167 parse_path(path, po)
168 rescue
169 puts(_("Error parsing %{path}") % {:path => path})
170 raise
171 end
172 end
173 po
174 end
175
176 private
177 def now
178 Time.now
179 end
180
181 def header_comment
182 <<-COMMENT
183 SOME DESCRIPTIVE TITLE.
184 Copyright (C) #{@copyright_year} #{@copyright_holder}
185 This file is distributed under the same license as the #{@package_name} package.
186 FIRST AUTHOR <EMAIL@ADDRESS>, #{@copyright_year}.
187
188 COMMENT
189 end
190
191 def header_content
192 time = now.strftime("%Y-%m-%d %H:%M%z")
193
194 <<-CONTENT
195 Project-Id-Version: #{@package_name} #{@package_version}
196 Report-Msgid-Bugs-To: #{@msgid_bugs_address}
197 POT-Creation-Date: #{time}
198 PO-Revision-Date: #{time}
199 Last-Translator: FULL NAME <EMAIL@ADDRESS>
200 Language-Team: LANGUAGE <LL@li.org>
201 Language:
202 MIME-Version: 1.0
203 Content-Type: text/plain; charset=#{@output_encoding}
204 Content-Transfer-Encoding: 8bit
205 Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;
206 CONTENT
207 end
208
209 def generate_pot(paths) # :nodoc:
210 header = POEntry.new(:normal)
211 header.msgid = ""
212 header.msgstr = header_content
213 header.translator_comment = header_comment
214 header.flags << "fuzzy"
215
216 po = parse(paths)
217 po.order = @po_order
218 po[header.msgid] = header
219
220 to_s_options = @po_format_options.merge(:encoding => @output_encoding)
221 po.to_s(to_s_options)
222 end
223
224 def check_command_line_options(*options) # :nodoc:
225 input_files, output = parse_arguments(*options)
226
227 if input_files.empty?
228 raise ArgumentError, _("no input files")
229 end
230
231 output ||= STDOUT
232
233 @input_files = input_files
234 @output = output
235 end
236
237 def parse_arguments(*options) #:nodoc:
238 output = nil
239
240 parser = OptionParser.new
241 banner = _("Usage: %s input.rb [-r parser.rb] [-o output.pot]") % $0
242 parser.banner = banner
243 parser.separator("")
244 description = _("Extract translatable strings from given input files.")
245 parser.separator(description)
246 parser.separator("")
247 parser.separator(_("Specific options:"))
248
249 parser.on("-o", "--output=FILE",
250 _("write output to specified file")) do |out|
251 output = out
252 end
253
254 parser.on("--package-name=NAME",
255 _("set package name in output"),
256 "(#{@package_name})") do |name|
257 @package_name = name
258 end
259
260 parser.on("--package-version=VERSION",
261 _("set package version in output"),
262 "(#{@package_version})") do |version|
263 @package_version = version
264 end
265
266 parser.on("--msgid-bugs-address=EMAIL",
267 _("set report e-mail address for msgid bugs"),
268 "(#{@msgid_bugs_address})") do |address|
269 @msgid_bugs_address = address
270 end
271
272 parser.on("--copyright-holder=HOLDER",
273 _("set copyright holder in output"),
274 "(#{@copyright_holder})") do |holder|
275 @copyright_holder = holder
276 end
277
278 parser.on("--copyright-year=YEAR",
279 _("set copyright year in output"),
280 "(#{@copyright_year})") do |year|
281 @copyright_year = year
282 end
283
284 parser.on("--output-encoding=ENCODING",
285 _("set encoding for output"),
286 "(#{@output_encoding})") do |encoding|
287 @output_encoding = encoding
288 end
289
290 parser.on("--[no-]sort-output",
291 _("Generate sorted output")) do |sort|
292 @po_order = sort ? :references : nil
293 end
294
295 parser.on("--[no-]sort-by-file",
296 _("Sort output by file location")) do |sort_by_file|
297 @po_order = sort_by_file ? :references : :msgid
298 end
299
300 parser.on("--[no-]sort-by-msgid",
301 _("Sort output by msgid")) do |sort_by_msgid|
302 @po_order = sort_by_msgid ? :msgid : :references
303 end
304
305 parser.on("--[no-]location",
306 _("Preserve '#: FILENAME:LINE' lines")) do |location|
307 @po_format_options[:include_reference_comment] = location
308 end
309
310 parser.on("--width=WIDTH", Integer,
311 _("Set output page width"),
312 "(#{@po_format_options[:max_line_width]})") do |width|
313 @po_format_options[:max_line_width] = width
314 end
315
316 parser.on("--[no-]wrap",
317 _("Break long message lines, longer than the output page width, into several lines"),
318 "(#{@po_format_options[:max_line_width] >= 0})") do |wrap|
319 if wrap
320 max_line_width = POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH
321 else
322 max_line_width = -1
323 end
324 @po_format_options[:max_line_width] = max_line_width
325 end
326
327 parser.on("-r", "--require=library",
328 _("require the library before executing xgettext")) do |out|
329 require out
330 end
331
332 parser.on("-c", "--add-comments[=TAG]",
333 _("If TAG is specified, place comment blocks starting with TAG and precedding keyword lines in output file"),
334 _("If TAG is not specified, place all comment blocks preceing keyword lines in output file"),
335 _("(default: %s)") % _("no TAG")) do |tag|
336 @parse_options[:comment_tag] = tag
337 end
338
339 parser.on("-d", "--debug", _("run in debugging mode")) do
340 $DEBUG = true
341 end
342
343 parser.on("-h", "--help", _("display this help and exit")) do
344 puts(parser.help)
345 exit(true)
346 end
347
348 parser.on_tail("--version", _("display version information and exit")) do
349 puts(GetText::VERSION)
350 exit(true)
351 end
352
353 parser.parse!(options)
354
355 [options, output]
356 end
357
358 def parse_path(path, po)
359 @parsers.each do |parser|
360 next unless parser.target?(path)
361
362 # For backward compatibility
363 if parser.method(:parse).arity == 1 or @parse_options.empty?
364 extracted_po = parser.parse(path)
365 else
366 extracted_po = parser.parse(path, @parse_options)
367 end
368 extracted_po.each do |po_entry|
369 if po_entry.kind_of?(Array)
370 po_entry = create_po_entry(*po_entry)
371 end
372
373 if po_entry.msgid.empty?
374 warn _("Warning: The empty \"\" msgid is reserved by " +
375 "gettext. So gettext(\"\") doesn't returns " +
376 "empty string but the header entry in po file.")
377 # TODO: add pommesage.reference to the pot header as below:
378 # # SOME DESCRIPTIVE TITLE.
379 # # Copyright (C) YEAR THE COPYRIGHT HOLDER
380 # # This file is distributed under the same license as the PACKAGE package.
381 # # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
382 # #
383 # #: test/test_gettext.rb:65
384 # #, fuzzy
385 # "#: test/test_gettext.rb:65" line is added.
386 next
387 end
388
389 if @output.is_a?(String)
390 base_path = Pathname.new(@output).dirname.expand_path
391 po_entry.references = po_entry.references.collect do |reference|
392 path, line, = reference.split(/:(\d+)\z/, 2)
393 absolute_path = Pathname.new(path).expand_path
394 begin
395 path = absolute_path.relative_path_from(base_path).to_s
396 rescue ArgumentError
397 raise # Should we ignore it?
398 end
399 "#{path}:#{line}"
400 end
401 end
402
403 existing_entry = po[po_entry.msgctxt, po_entry.msgid]
404 if existing_entry
405 po_entry = existing_entry.merge(po_entry)
406 end
407 po[po_entry.msgctxt, po_entry.msgid] = po_entry
408 end
409 break
410 end
411 end
412
413 def create_po_entry(msgid, *references)
414 type = :normal
415 msgctxt = nil
416 msgid_plural = nil
417
418 if msgid.include?("\004")
419 msgctxt, msgid = msgid.split(/\004/, 2)
420 type = :msgctxt
421 end
422 if msgid.include?("\000")
423 msgid, msgid_plural = msgid.split(/\000/, 2)
424 if type == :msgctxt
425 type = :msgctxt_plural
426 else
427 type = :plural
428 end
429 end
430
431 po_entry = POEntry.new(type)
432 po_entry.msgid = msgid
433 po_entry.msgctxt = msgctxt
434 po_entry.msgid_plural = msgid_plural
435 po_entry.references = references
436 po_entry
437 end
438 end
439 end
440 end
+0
-26
vendor/gettext/tools.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2005-2008 Masao Mutoh
4 #
5 # License: Ruby's or LGPL
6 #
7 # This library is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Lesser General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Lesser General Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 require "gettext/tools/xgettext"
21 require "gettext/tools/msgfmt"
22 require "gettext/tools/msginit"
23 require "gettext/tools/msgmerge"
24 require "gettext/tools/msgcat"
25 require "gettext/mo"
+0
-13
vendor/gettext/version.rb less more
0 =begin
1 version - version information of gettext
2
3 Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
4 Copyright (C) 2005-2009 Masao Mutoh
5
6 You may redistribute it and/or modify it under the same
7 license terms as Ruby or LGPL.
8 =end
9
10 module GetText
11 VERSION = "3.0.9"
12 end
+0
-296
vendor/gettext.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin
3 gettext.rb - GetText module
4
5 Copyright (C) 2001-2010 Masao Mutoh
6 Copyright (C) 2001-2003 Masahiro Sakai
7
8 Masao Mutoh <mutomasa at gmail.com>
9 Masahiro Sakai <s01397ms@sfc.keio.ac.jp>
10
11 You may redistribute it and/or modify it under the same
12 license terms as Ruby or LGPL.
13 =end
14
15 require 'locale'
16
17 require 'gettext/version'
18 require 'gettext/text_domain_manager'
19
20 module GetText
21 # If the text domain isn't bound when calling GetText.textdomain, this error is raised.
22 class NoboundTextDomainError < RuntimeError
23 def initialize(domainname)
24 @domainname = domainname
25 end
26 def message
27 "#{@domainname} is not bound."
28 end
29 end
30
31 extend self
32
33 def self.included(mod) #:nodoc:
34 mod.extend self
35 end
36
37 # bindtextdomain(domainname, options = {})
38 #
39 # Bind a text domain(%{path}/%{locale}/LC_MESSAGES/%{domainname}.mo) to
40 # your program.
41 # Normally, the texdomain scope becomes the class/module(and parent
42 # classes/included modules).
43 #
44 # * domainname: the text domain name.
45 # * options: options as an Hash.
46 # * :path - the path to the mo-files. When the value is nil, it will search default paths such as
47 # /usr/share/locale, /usr/local/share/locale)
48 # * :output_charset - The output charset. Same with GetText.set_output_charset. Usually, L10n
49 # library doesn't use this option. Application may use this once.
50 # * Returns: the GetText::TextDomainManager.
51 #
52 def bindtextdomain(domainname, *options)
53 bindtextdomain_to(self, domainname, *options)
54 end
55
56 # Includes GetText module and bind a text domain to a class.
57 # * klass: the target ruby class.
58 # * domainname: the text domain name.
59 # * options: options as an Hash. See GetText.bindtextdomain.
60 def bindtextdomain_to(klass, domainname, *options)
61 if options[0].kind_of? Hash
62 opts = options[0]
63 else
64 # for backward compatibility.
65 opts = {}
66 opts[:path] = options[0] if options[0]
67 opts[:output_charset] = options[2] if options[2]
68 end
69 unless (klass.kind_of? GetText or klass.include? GetText)
70 klass.__send__(:include, GetText)
71 end
72 TextDomainManager.bind_to(klass, domainname, opts)
73 end
74
75 # Binds a existed text domain to your program.
76 # This is the same function with GetText.bindtextdomain but simpler(and faster) than bindtextdomain.
77 # Note that you need to call GetText.bindtextdomain first. If the domainname hasn't bound yet,
78 # raises GetText::NoboundTextDomainError.
79 # * domainname: a text domain name.
80 # * Returns: the GetText::TextDomainManager.
81 def textdomain(domainname) #:nodoc:
82 textdomain_to(self, domainname)
83 end
84
85 # Includes GetText module and bind an exsited text domain to a class.
86 # See text domain for more detail.
87 # * klass: the target ruby class.
88 # * domainname: the text domain name.
89
90 def textdomain_to(klass, domainname) #:nodoc:
91 domain = TextDomainManager.text_domain_pool(domainname)
92 raise NoboundTextDomainError.new(domainname) unless domain
93 bindtextdomain_to(klass, domainname)
94 end
95
96 # call-seq:
97 # gettext(msgid)
98 # _(msgid)
99 #
100 # Translates msgid and return the message.
101 # This doesn't make a copy of the message.
102 #
103 # You need to use String#dup if you want to modify the return value
104 # with destructive functions.
105 #
106 # (e.g.1) _("Hello ").dup << "world"
107 #
108 # But e.g.1 should be rewrite to:
109 #
110 # (e.g.2) _("Hello %{val}") % {:val => "world"}
111 #
112 # Because the translator may want to change the position of "world".
113 #
114 # * msgid: the message id.
115 # * Returns: localized text by msgid. If there are not binded mo-file, it will return msgid.
116 def gettext(msgid)
117 TextDomainManager.translate_singular_message(self, msgid)
118 end
119
120 # call-seq:
121 # sgettext(msgid, div = '|')
122 # s_(msgid, div = '|')
123 #
124 # Translates msgid, but if there are no localized text,
125 # it returns a last part of msgid separeted "div".
126 #
127 # * msgid: the message id.
128 # * separator: separator or nil for no seperation.
129 # * Returns: the localized text by msgid. If there are no localized text,
130 # it returns a last part of the msgid separeted by "seperator".
131 # <tt>Movie|Location -> Location</tt>
132 # See: http://www.gnu.org/software/gettext/manual/html_mono/gettext.html#SEC151
133 def sgettext(msgid, seperator = "|")
134 TextDomainManager.translate_singular_message(self, msgid, seperator)
135 end
136
137 # call-seq:
138 # pgettext(msgctxt, msgid)
139 # p_(msgctxt, msgid)
140 #
141 # Translates msgid with msgctxt. This methods is similer with s_().
142 # e.g.) p_("File", "New") == s_("File|New")
143 # p_("File", "Open") == s_("File|Open")
144 #
145 # * msgctxt: the message context.
146 # * msgid: the message id.
147 # * Returns: the localized text by msgid. If there are no localized text,
148 # it returns msgid.
149 # See: http://www.gnu.org/software/autoconf/manual/gettext/Contexts.html
150 def pgettext(msgctxt, msgid)
151 TextDomainManager.translate_singular_message(self, "#{msgctxt}\004#{msgid}", "\004")
152 end
153
154 # call-seq:
155 # ngettext(msgid, msgid_plural, n)
156 # ngettext(msgids, n) # msgids = [msgid, msgid_plural]
157 # n_(msgid, msgid_plural, n)
158 # n_(msgids, n) # msgids = [msgid, msgid_plural]
159 #
160 # The ngettext is similar to the gettext function as it finds the message catalogs in the same way.
161 # But it takes two extra arguments for plural form.
162 #
163 # * msgid: the singular form.
164 # * msgid_plural: the plural form.
165 # * n: a number used to determine the plural form.
166 # * Returns: the localized text which key is msgid_plural if n is plural(follow plural-rule) or msgid.
167 # "plural-rule" is defined in po-file.
168 def ngettext(msgid, msgid_plural, n = nil)
169 TextDomainManager.translate_plural_message(self, msgid, msgid_plural, n)
170 end
171
172 # call-seq:
173 # nsgettext(msgid, msgid_plural, n, div = "|")
174 # nsgettext(msgids, n, div = "|") # msgids = [msgid, msgid_plural]
175 # ns_(msgid, msgid_plural, n, div = "|")
176 # ns_(msgids, n, div = "|") # msgids = [msgid, msgid_plural]
177 #
178 # The nsgettext is similar to the ngettext.
179 # But if there are no localized text,
180 # it returns a last part of msgid separeted "div".
181 #
182 # * msgid: the singular form with "div". (e.g. "Special|An apple")
183 # * msgid_plural: the plural form. (e.g. "%{num} Apples")
184 # * n: a number used to determine the plural form.
185 # * Returns: the localized text which key is msgid_plural if n is plural(follow plural-rule) or msgid.
186 # "plural-rule" is defined in po-file.
187 def nsgettext(msgid, msgid_plural, n="|", seperator = "|")
188 TextDomainManager.translate_plural_message(self, msgid, msgid_plural, n, seperator)
189 end
190
191 # call-seq:
192 # npgettext(msgctxt, msgid, msgid_plural, n)
193 # npgettext(msgctxt, msgids, n) # msgids = [msgid, msgid_plural]
194 # np_(msgctxt, msgid, msgid_plural, n)
195 # np_(msgctxt, msgids, n) # msgids = [msgid, msgid_plural]
196 #
197 # The npgettext is similar to the nsgettext function.
198 # e.g.) np_("Special", "An apple", "%{num} Apples", num) == ns_("Special|An apple", "%{num} Apples", num)
199 # * msgctxt: the message context.
200 # * msgid: the singular form.
201 # * msgid_plural: the plural form.
202 # * n: a number used to determine the plural form.
203 # * Returns: the localized text which key is msgid_plural if n is plural(follow plural-rule) or msgid.
204 # "plural-rule" is defined in po-file.
205 def npgettext(msgctxt, msgids, arg2 = nil, arg3 = nil)
206 if msgids.kind_of?(Array)
207 msgid = msgids[0]
208 msgid_ctxt = "#{msgctxt}\004#{msgid}"
209 msgid_plural = msgids[1]
210 opt1 = arg2
211 opt2 = arg3
212 else
213 msgid = msgids
214 msgid_ctxt = "#{msgctxt}\004#{msgid}"
215 msgid_plural = arg2
216 opt1 = arg3
217 opt2 = nil
218 end
219
220 msgstr = TextDomainManager.translate_plural_message(self, msgid_ctxt, msgid_plural, opt1, opt2)
221 if msgstr == msgid_ctxt
222 msgid
223 else
224 msgstr
225 end
226 end
227
228 # makes dynamic translation messages readable for the gettext parser.
229 # <tt>_(fruit)</tt> cannot be understood by the gettext parser. To help the parser find all your translations,
230 # you can add <tt>fruit = N_("Apple")</tt> which does not translate, but tells the parser: "Apple" needs translation.
231 # * msgid: the message id.
232 # * Returns: msgid.
233 def N_(msgid)
234 msgid
235 end
236
237 # This is same function as N_ but for ngettext.
238 # * msgid: the message id.
239 # * msgid_plural: the plural message id.
240 # * Returns: msgid.
241 def Nn_(msgid, msgid_plural)
242 [msgid, msgid_plural]
243 end
244
245 # Sets charset(String) such as "euc-jp", "sjis", "CP932", "utf-8", ...
246 # You shouldn't use this in your own Libraries.
247 # * charset: an output_charset
248 # * Returns: self
249 def set_output_charset(charset)
250 TextDomainManager.output_charset = charset
251 self
252 end
253
254 # Gets the current output_charset which is set using GetText.set_output_charset.
255 # * Returns: output_charset.
256 def output_charset
257 TextDomainManager.output_charset
258 end
259
260 # Set the locale. This value forces the locale whole the programs.
261 # This method calls Locale.set_app_language_tags, Locale.default, Locale.current.
262 # Use Locale methods if you need to handle locales more flexible.
263 def set_locale(lang)
264 Locale.set_app_language_tags(lang)
265 Locale.default = lang
266 Locale.current = lang
267 end
268
269 # Set the locale to the current thread.
270 # Note that if #set_locale is set, this value is ignored.
271 # If you need, set_locale(nil); set_current_locale(lang)
272 def set_current_locale(lang)
273 Locale.current = lang
274 end
275
276 def locale
277 Locale.current[0]
278 end
279
280 alias :locale= :set_locale #:nodoc:
281 alias :current_locale= :set_current_locale #:nodoc:
282 alias :_ :gettext #:nodoc:
283 alias :n_ :ngettext #:nodoc:
284 alias :s_ :sgettext #:nodoc:
285 alias :ns_ :nsgettext #:nodoc:
286 alias :np_ :npgettext #:nodoc:
287
288 alias :output_charset= :set_output_charset #:nodoc:
289
290 unless defined? XX
291 # This is the workaround to conflict p_ methods with the xx("double x") library.
292 # http://rubyforge.org/projects/codeforpeople/
293 alias :p_ :pgettext #:nodoc:
294 end
295 end
+0
-11
vendor/hmac-md5.rb less more
0 require 'hmac'
1 require 'digest/md5'
2
3 module HMAC
4 class MD5 < Base
5 def initialize(key = nil)
6 super(Digest::MD5, 64, 16, key)
7 end
8 public_class_method :new, :digest, :hexdigest
9 end
10 end
+0
-11
vendor/hmac-rmd160.rb less more
0 require 'hmac'
1 require 'digest/rmd160'
2
3 module HMAC
4 class RMD160 < Base
5 def initialize(key = nil)
6 super(Digest::RMD160, 64, 20, key)
7 end
8 public_class_method :new, :digest, :hexdigest
9 end
10 end
+0
-11
vendor/hmac-sha1.rb less more
0 require 'hmac'
1 require 'digest/sha1'
2
3 module HMAC
4 class SHA1 < Base
5 def initialize(key = nil)
6 super(Digest::SHA1, 64, 20, key)
7 end
8 public_class_method :new, :digest, :hexdigest
9 end
10 end
+0
-25
vendor/hmac-sha2.rb less more
0 require 'hmac'
1 require 'digest/sha2'
2
3 module HMAC
4 class SHA256 < Base
5 def initialize(key = nil)
6 super(Digest::SHA256, 64, 32, key)
7 end
8 public_class_method :new, :digest, :hexdigest
9 end
10
11 class SHA384 < Base
12 def initialize(key = nil)
13 super(Digest::SHA384, 128, 48, key)
14 end
15 public_class_method :new, :digest, :hexdigest
16 end
17
18 class SHA512 < Base
19 def initialize(key = nil)
20 super(Digest::SHA512, 128, 64, key)
21 end
22 public_class_method :new, :digest, :hexdigest
23 end
24 end
+0
-118
vendor/hmac.rb less more
0 # Copyright (C) 2001 Daiki Ueno <ueno@unixuser.org>
1 # This library is distributed under the terms of the Ruby license.
2
3 # This module provides common interface to HMAC engines.
4 # HMAC standard is documented in RFC 2104:
5 #
6 # H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication",
7 # RFC 2104, February 1997
8 #
9 # These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.
10 #
11 # <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>
12 #
13 # Source repository is at
14 #
15 # http://github.com/topfunky/ruby-hmac/tree/master
16
17 module HMAC
18
19 VERSION = '0.4.0'
20
21 class Base
22 def initialize(algorithm, block_size, output_length, key)
23 @algorithm = algorithm
24 @block_size = block_size
25 @output_length = output_length
26 @initialized = false
27 @key_xor_ipad = ''
28 @key_xor_opad = ''
29 set_key(key) unless key.nil?
30 end
31
32 private
33 def check_status
34 unless @initialized
35 raise RuntimeError,
36 "The underlying hash algorithm has not yet been initialized."
37 end
38 end
39
40 public
41 def set_key(key)
42 # If key is longer than the block size, apply hash function
43 # to key and use the result as a real key.
44 key = @algorithm.digest(key) if key.size > @block_size
45 akey = key.unpack("C*")
46 key_xor_ipad = ("\x36" * @block_size).unpack("C*")
47 key_xor_opad = ("\x5C" * @block_size).unpack("C*")
48 for i in 0 .. akey.size - 1
49 key_xor_ipad[i] ^= akey[i]
50 key_xor_opad[i] ^= akey[i]
51 end
52 @key_xor_ipad = key_xor_ipad.pack("C*")
53 @key_xor_opad = key_xor_opad.pack("C*")
54 @md = @algorithm.new
55 @initialized = true
56 end
57
58 def reset_key
59 @key_xor_ipad.gsub!(/./, '?')
60 @key_xor_opad.gsub!(/./, '?')
61 @key_xor_ipad[0..-1] = ''
62 @key_xor_opad[0..-1] = ''
63 @initialized = false
64 end
65
66 def update(text)
67 check_status
68 # perform inner H
69 md = @algorithm.new
70 md.update(@key_xor_ipad)
71 md.update(text)
72 str = md.digest
73 # perform outer H
74 md = @algorithm.new
75 md.update(@key_xor_opad)
76 md.update(str)
77 @md = md
78 end
79 alias << update
80
81 def digest
82 check_status
83 @md.digest
84 end
85
86 def hexdigest
87 check_status
88 @md.hexdigest
89 end
90 alias to_s hexdigest
91
92 # These two class methods below are safer than using above
93 # instance methods combinatorially because an instance will have
94 # held a key even if it's no longer in use.
95 def Base.digest(key, text)
96 hmac = self.new(key)
97 begin
98 hmac.update(text)
99 hmac.digest
100 ensure
101 hmac.reset_key
102 end
103 end
104
105 def Base.hexdigest(key, text)
106 hmac = self.new(key)
107 begin
108 hmac.update(text)
109 hmac.hexdigest
110 ensure
111 hmac.reset_key
112 end
113 end
114
115 private_class_method :new, :digest, :hexdigest
116 end
117 end
+0
-28
vendor/json/add/bigdecimal.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3 defined?(::BigDecimal) or require 'bigdecimal'
4
5 class BigDecimal
6 # Import a JSON Marshalled object.
7 #
8 # method used for JSON marshalling support.
9 def self.json_create(object)
10 BigDecimal._load object['b']
11 end
12
13 # Marshal the object to JSON.
14 #
15 # method used for JSON marshalling support.
16 def as_json(*)
17 {
18 JSON.create_id => self.class.name,
19 'b' => _dump,
20 }
21 end
22
23 # return the JSON value
24 def to_json(*)
25 as_json.to_json
26 end
27 end
+0
-28
vendor/json/add/complex.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3 defined?(::Complex) or require 'complex'
4
5 class Complex
6
7 # Deserializes JSON string by converting Real value <tt>r</tt>, imaginary
8 # value <tt>i</tt>, to a Complex object.
9 def self.json_create(object)
10 Complex(object['r'], object['i'])
11 end
12
13 # Returns a hash, that will be turned into a JSON object and represent this
14 # object.
15 def as_json(*)
16 {
17 JSON.create_id => self.class.name,
18 'r' => real,
19 'i' => imag,
20 }
21 end
22
23 # Stores class name (Complex) along with real value <tt>r</tt> and imaginary value <tt>i</tt> as JSON string
24 def to_json(*)
25 as_json.to_json
26 end
27 end
+0
-11
vendor/json/add/core.rb less more
0 # This file requires the implementations of ruby core's custom objects for
1 # serialisation/deserialisation.
2
3 require 'json/add/date'
4 require 'json/add/date_time'
5 require 'json/add/exception'
6 require 'json/add/range'
7 require 'json/add/regexp'
8 require 'json/add/struct'
9 require 'json/add/symbol'
10 require 'json/add/time'
+0
-34
vendor/json/add/date.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3 require 'date'
4
5 # Date serialization/deserialization
6 class Date
7
8 # Deserializes JSON string by converting Julian year <tt>y</tt>, month
9 # <tt>m</tt>, day <tt>d</tt> and Day of Calendar Reform <tt>sg</tt> to Date.
10 def self.json_create(object)
11 civil(*object.values_at('y', 'm', 'd', 'sg'))
12 end
13
14 alias start sg unless method_defined?(:start)
15
16 # Returns a hash, that will be turned into a JSON object and represent this
17 # object.
18 def as_json(*)
19 {
20 JSON.create_id => self.class.name,
21 'y' => year,
22 'm' => month,
23 'd' => day,
24 'sg' => start,
25 }
26 end
27
28 # Stores class name (Date) with Julian year <tt>y</tt>, month <tt>m</tt>, day
29 # <tt>d</tt> and Day of Calendar Reform <tt>sg</tt> as JSON string
30 def to_json(*args)
31 as_json.to_json(*args)
32 end
33 end
+0
-50
vendor/json/add/date_time.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3 require 'date'
4
5 # DateTime serialization/deserialization
6 class DateTime
7
8 # Deserializes JSON string by converting year <tt>y</tt>, month <tt>m</tt>,
9 # day <tt>d</tt>, hour <tt>H</tt>, minute <tt>M</tt>, second <tt>S</tt>,
10 # offset <tt>of</tt> and Day of Calendar Reform <tt>sg</tt> to DateTime.
11 def self.json_create(object)
12 args = object.values_at('y', 'm', 'd', 'H', 'M', 'S')
13 of_a, of_b = object['of'].split('/')
14 if of_b and of_b != '0'
15 args << Rational(of_a.to_i, of_b.to_i)
16 else
17 args << of_a
18 end
19 args << object['sg']
20 civil(*args)
21 end
22
23 alias start sg unless method_defined?(:start)
24
25 # Returns a hash, that will be turned into a JSON object and represent this
26 # object.
27 def as_json(*)
28 {
29 JSON.create_id => self.class.name,
30 'y' => year,
31 'm' => month,
32 'd' => day,
33 'H' => hour,
34 'M' => min,
35 'S' => sec,
36 'of' => offset.to_s,
37 'sg' => start,
38 }
39 end
40
41 # Stores class name (DateTime) with Julian year <tt>y</tt>, month <tt>m</tt>,
42 # day <tt>d</tt>, hour <tt>H</tt>, minute <tt>M</tt>, second <tt>S</tt>,
43 # offset <tt>of</tt> and Day of Calendar Reform <tt>sg</tt> as JSON string
44 def to_json(*args)
45 as_json.to_json(*args)
46 end
47 end
48
49
+0
-31
vendor/json/add/exception.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3
4 # Exception serialization/deserialization
5 class Exception
6
7 # Deserializes JSON string by constructing new Exception object with message
8 # <tt>m</tt> and backtrace <tt>b</tt> serialized with <tt>to_json</tt>
9 def self.json_create(object)
10 result = new(object['m'])
11 result.set_backtrace object['b']
12 result
13 end
14
15 # Returns a hash, that will be turned into a JSON object and represent this
16 # object.
17 def as_json(*)
18 {
19 JSON.create_id => self.class.name,
20 'm' => message,
21 'b' => backtrace,
22 }
23 end
24
25 # Stores class name (Exception) with message <tt>m</tt> and backtrace array
26 # <tt>b</tt> as JSON string
27 def to_json(*args)
28 as_json.to_json(*args)
29 end
30 end
+0
-31
vendor/json/add/ostruct.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3 require 'ostruct'
4
5 # OpenStruct serialization/deserialization
6 class OpenStruct
7
8 # Deserializes JSON string by constructing new Struct object with values
9 # <tt>v</tt> serialized by <tt>to_json</tt>.
10 def self.json_create(object)
11 new(object['t'] || object[:t])
12 end
13
14 # Returns a hash, that will be turned into a JSON object and represent this
15 # object.
16 def as_json(*)
17 klass = self.class.name
18 klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
19 {
20 JSON.create_id => klass,
21 't' => table,
22 }
23 end
24
25 # Stores class name (OpenStruct) with this struct's values <tt>v</tt> as a
26 # JSON string.
27 def to_json(*args)
28 as_json.to_json(*args)
29 end
30 end
+0
-29
vendor/json/add/range.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3
4 # Range serialization/deserialization
5 class Range
6
7 # Deserializes JSON string by constructing new Range object with arguments
8 # <tt>a</tt> serialized by <tt>to_json</tt>.
9 def self.json_create(object)
10 new(*object['a'])
11 end
12
13 # Returns a hash, that will be turned into a JSON object and represent this
14 # object.
15 def as_json(*)
16 {
17 JSON.create_id => self.class.name,
18 'a' => [ first, last, exclude_end? ]
19 }
20 end
21
22 # Stores class name (Range) with JSON array of arguments <tt>a</tt> which
23 # include <tt>first</tt> (integer), <tt>last</tt> (integer), and
24 # <tt>exclude_end?</tt> (boolean) as JSON string.
25 def to_json(*args)
26 as_json.to_json(*args)
27 end
28 end
+0
-27
vendor/json/add/rational.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3 defined?(::Rational) or require 'rational'
4
5 class Rational
6 # Deserializes JSON string by converting numerator value <tt>n</tt>,
7 # denominator value <tt>d</tt>, to a Rational object.
8 def self.json_create(object)
9 Rational(object['n'], object['d'])
10 end
11
12 # Returns a hash, that will be turned into a JSON object and represent this
13 # object.
14 def as_json(*)
15 {
16 JSON.create_id => self.class.name,
17 'n' => numerator,
18 'd' => denominator,
19 }
20 end
21
22 # Stores class name (Rational) along with numerator value <tt>n</tt> and denominator value <tt>d</tt> as JSON string
23 def to_json(*)
24 as_json.to_json
25 end
26 end
+0
-30
vendor/json/add/regexp.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3
4 # Regexp serialization/deserialization
5 class Regexp
6
7 # Deserializes JSON string by constructing new Regexp object with source
8 # <tt>s</tt> (Regexp or String) and options <tt>o</tt> serialized by
9 # <tt>to_json</tt>
10 def self.json_create(object)
11 new(object['s'], object['o'])
12 end
13
14 # Returns a hash, that will be turned into a JSON object and represent this
15 # object.
16 def as_json(*)
17 {
18 JSON.create_id => self.class.name,
19 'o' => options,
20 's' => source,
21 }
22 end
23
24 # Stores class name (Regexp) with options <tt>o</tt> and source <tt>s</tt>
25 # (Regexp or String) as JSON string
26 def to_json(*)
27 as_json.to_json
28 end
29 end
+0
-30
vendor/json/add/struct.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3
4 # Struct serialization/deserialization
5 class Struct
6
7 # Deserializes JSON string by constructing new Struct object with values
8 # <tt>v</tt> serialized by <tt>to_json</tt>.
9 def self.json_create(object)
10 new(*object['v'])
11 end
12
13 # Returns a hash, that will be turned into a JSON object and represent this
14 # object.
15 def as_json(*)
16 klass = self.class.name
17 klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
18 {
19 JSON.create_id => klass,
20 'v' => values,
21 }
22 end
23
24 # Stores class name (Struct) with Struct values <tt>v</tt> as a JSON string.
25 # Only named structs are supported.
26 def to_json(*args)
27 as_json.to_json(*args)
28 end
29 end
+0
-25
vendor/json/add/symbol.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3
4 # Symbol serialization/deserialization
5 class Symbol
6 # Returns a hash, that will be turned into a JSON object and represent this
7 # object.
8 def as_json(*)
9 {
10 JSON.create_id => self.class.name,
11 's' => to_s,
12 }
13 end
14
15 # Stores class name (Symbol) with String representation of Symbol as a JSON string.
16 def to_json(*a)
17 as_json.to_json(*a)
18 end
19
20 # Deserializes JSON string by converting the <tt>string</tt> value stored in the object to a Symbol
21 def self.json_create(o)
22 o['s'].to_sym
23 end
24 end
+0
-38
vendor/json/add/time.rb less more
0 unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
1 require 'json'
2 end
3
4 # Time serialization/deserialization
5 class Time
6
7 # Deserializes JSON string by converting time since epoch to Time
8 def self.json_create(object)
9 if usec = object.delete('u') # used to be tv_usec -> tv_nsec
10 object['n'] = usec * 1000
11 end
12 if method_defined?(:tv_nsec)
13 at(object['s'], Rational(object['n'], 1000))
14 else
15 at(object['s'], object['n'] / 1000)
16 end
17 end
18
19 # Returns a hash, that will be turned into a JSON object and represent this
20 # object.
21 def as_json(*)
22 nanoseconds = [ tv_usec * 1000 ]
23 respond_to?(:tv_nsec) and nanoseconds << tv_nsec
24 nanoseconds = nanoseconds.max
25 {
26 JSON.create_id => self.class.name,
27 's' => tv_sec,
28 'n' => nanoseconds,
29 }
30 end
31
32 # Stores class name (Time) with number of seconds since epoch and number of
33 # microseconds for Time as JSON string
34 def to_json(*args)
35 as_json.to_json(*args)
36 end
37 end
+0
-484
vendor/json/common.rb less more
0 require 'json/version'
1 require 'json/generic_object'
2
3 module JSON
4 class << self
5 # If _object_ is string-like, parse the string and return the parsed result
6 # as a Ruby data structure. Otherwise generate a JSON text from the Ruby
7 # data structure object and return it.
8 #
9 # The _opts_ argument is passed through to generate/parse respectively. See
10 # generate and parse for their documentation.
11 def [](object, opts = {})
12 if object.respond_to? :to_str
13 JSON.parse(object.to_str, opts)
14 else
15 JSON.generate(object, opts)
16 end
17 end
18
19 # Returns the JSON parser class that is used by JSON. This is either
20 # JSON::Ext::Parser or JSON::Pure::Parser.
21 attr_reader :parser
22
23 # Set the JSON parser class _parser_ to be used by JSON.
24 def parser=(parser) # :nodoc:
25 @parser = parser
26 remove_const :Parser if JSON.const_defined_in?(self, :Parser)
27 const_set :Parser, parser
28 end
29
30 # Return the constant located at _path_. The format of _path_ has to be
31 # either ::A::B::C or A::B::C. In any case, A has to be located at the top
32 # level (absolute namespace path?). If there doesn't exist a constant at
33 # the given path, an ArgumentError is raised.
34 def deep_const_get(path) # :nodoc:
35 path.to_s.split(/::/).inject(Object) do |p, c|
36 case
37 when c.empty? then p
38 when JSON.const_defined_in?(p, c) then p.const_get(c)
39 else
40 begin
41 p.const_missing(c)
42 rescue NameError => e
43 raise ArgumentError, "can't get const #{path}: #{e}"
44 end
45 end
46 end
47 end
48
49 # Set the module _generator_ to be used by JSON.
50 def generator=(generator) # :nodoc:
51 old, $VERBOSE = $VERBOSE, nil
52 @generator = generator
53 generator_methods = generator::GeneratorMethods
54 for const in generator_methods.constants
55 klass = deep_const_get(const)
56 modul = generator_methods.const_get(const)
57 klass.class_eval do
58 instance_methods(false).each do |m|
59 m.to_s == 'to_json' and remove_method m
60 end
61 include modul
62 end
63 end
64 self.state = generator::State
65 const_set :State, self.state
66 const_set :SAFE_STATE_PROTOTYPE, State.new
67 const_set :FAST_STATE_PROTOTYPE, State.new(
68 :indent => '',
69 :space => '',
70 :object_nl => "",
71 :array_nl => "",
72 :max_nesting => false
73 )
74 const_set :PRETTY_STATE_PROTOTYPE, State.new(
75 :indent => ' ',
76 :space => ' ',
77 :object_nl => "\n",
78 :array_nl => "\n"
79 )
80 ensure
81 $VERBOSE = old
82 end
83
84 # Returns the JSON generator module that is used by JSON. This is
85 # either JSON::Ext::Generator or JSON::Pure::Generator.
86 attr_reader :generator
87
88 # Returns the JSON generator state class that is used by JSON. This is
89 # either JSON::Ext::Generator::State or JSON::Pure::Generator::State.
90 attr_accessor :state
91
92 # This is create identifier, which is used to decide if the _json_create_
93 # hook of a class should be called. It defaults to 'json_class'.
94 attr_accessor :create_id
95 end
96 self.create_id = 'json_class'
97
98 NaN = 0.0/0
99
100 Infinity = 1.0/0
101
102 MinusInfinity = -Infinity
103
104 # The base exception for JSON errors.
105 class JSONError < StandardError
106 def self.wrap(exception)
107 obj = new("Wrapped(#{exception.class}): #{exception.message.inspect}")
108 obj.set_backtrace exception.backtrace
109 obj
110 end
111 end
112
113 # This exception is raised if a parser error occurs.
114 class ParserError < JSONError; end
115
116 # This exception is raised if the nesting of parsed data structures is too
117 # deep.
118 class NestingError < ParserError; end
119
120 # :stopdoc:
121 class CircularDatastructure < NestingError; end
122 # :startdoc:
123
124 # This exception is raised if a generator or unparser error occurs.
125 class GeneratorError < JSONError; end
126 # For backwards compatibility
127 UnparserError = GeneratorError
128
129 # This exception is raised if the required unicode support is missing on the
130 # system. Usually this means that the iconv library is not installed.
131 class MissingUnicodeSupport < JSONError; end
132
133 module_function
134
135 # Parse the JSON document _source_ into a Ruby data structure and return it.
136 #
137 # _opts_ can have the following
138 # keys:
139 # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
140 # structures. Disable depth checking with :max_nesting => false. It defaults
141 # to 100.
142 # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
143 # defiance of RFC 4627 to be parsed by the Parser. This option defaults
144 # to false.
145 # * *symbolize_names*: If set to true, returns symbols for the names
146 # (keys) in a JSON object. Otherwise strings are returned. Strings are
147 # the default.
148 # * *create_additions*: If set to false, the Parser doesn't create
149 # additions even if a matching class and create_id was found. This option
150 # defaults to false.
151 # * *object_class*: Defaults to Hash
152 # * *array_class*: Defaults to Array
153 def parse(source, opts = {})
154 Parser.new(source, opts).parse
155 end
156
157 # Parse the JSON document _source_ into a Ruby data structure and return it.
158 # The bang version of the parse method defaults to the more dangerous values
159 # for the _opts_ hash, so be sure only to parse trusted _source_ documents.
160 #
161 # _opts_ can have the following keys:
162 # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
163 # structures. Enable depth checking with :max_nesting => anInteger. The parse!
164 # methods defaults to not doing max depth checking: This can be dangerous
165 # if someone wants to fill up your stack.
166 # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
167 # defiance of RFC 4627 to be parsed by the Parser. This option defaults
168 # to true.
169 # * *create_additions*: If set to false, the Parser doesn't create
170 # additions even if a matching class and create_id was found. This option
171 # defaults to false.
172 def parse!(source, opts = {})
173 opts = {
174 :max_nesting => false,
175 :allow_nan => true
176 }.update(opts)
177 Parser.new(source, opts).parse
178 end
179
180 # Generate a JSON document from the Ruby data structure _obj_ and return
181 # it. _state_ is * a JSON::State object,
182 # * or a Hash like object (responding to to_hash),
183 # * an object convertible into a hash by a to_h method,
184 # that is used as or to configure a State object.
185 #
186 # It defaults to a state object, that creates the shortest possible JSON text
187 # in one line, checks for circular data structures and doesn't allow NaN,
188 # Infinity, and -Infinity.
189 #
190 # A _state_ hash can have the following keys:
191 # * *indent*: a string used to indent levels (default: ''),
192 # * *space*: a string that is put after, a : or , delimiter (default: ''),
193 # * *space_before*: a string that is put before a : pair delimiter (default: ''),
194 # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
195 # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
196 # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
197 # generated, otherwise an exception is thrown if these values are
198 # encountered. This options defaults to false.
199 # * *max_nesting*: The maximum depth of nesting allowed in the data
200 # structures from which JSON is to be generated. Disable depth checking
201 # with :max_nesting => false, it defaults to 100.
202 #
203 # See also the fast_generate for the fastest creation method with the least
204 # amount of sanity checks, and the pretty_generate method for some
205 # defaults for pretty output.
206 def generate(obj, opts = nil)
207 if State === opts
208 state, opts = opts, nil
209 else
210 state = SAFE_STATE_PROTOTYPE.dup
211 end
212 if opts
213 if opts.respond_to? :to_hash
214 opts = opts.to_hash
215 elsif opts.respond_to? :to_h
216 opts = opts.to_h
217 else
218 raise TypeError, "can't convert #{opts.class} into Hash"
219 end
220 state = state.configure(opts)
221 end
222 state.generate(obj)
223 end
224
225 # :stopdoc:
226 # I want to deprecate these later, so I'll first be silent about them, and
227 # later delete them.
228 alias unparse generate
229 module_function :unparse
230 # :startdoc:
231
232 # Generate a JSON document from the Ruby data structure _obj_ and return it.
233 # This method disables the checks for circles in Ruby objects.
234 #
235 # *WARNING*: Be careful not to pass any Ruby data structures with circles as
236 # _obj_ argument because this will cause JSON to go into an infinite loop.
237 def fast_generate(obj, opts = nil)
238 if State === opts
239 state, opts = opts, nil
240 else
241 state = FAST_STATE_PROTOTYPE.dup
242 end
243 if opts
244 if opts.respond_to? :to_hash
245 opts = opts.to_hash
246 elsif opts.respond_to? :to_h
247 opts = opts.to_h
248 else
249 raise TypeError, "can't convert #{opts.class} into Hash"
250 end
251 state.configure(opts)
252 end
253 state.generate(obj)
254 end
255
256 # :stopdoc:
257 # I want to deprecate these later, so I'll first be silent about them, and later delete them.
258 alias fast_unparse fast_generate
259 module_function :fast_unparse
260 # :startdoc:
261
262 # Generate a JSON document from the Ruby data structure _obj_ and return it.
263 # The returned document is a prettier form of the document returned by
264 # #unparse.
265 #
266 # The _opts_ argument can be used to configure the generator. See the
267 # generate method for a more detailed explanation.
268 def pretty_generate(obj, opts = nil)
269 if State === opts
270 state, opts = opts, nil
271 else
272 state = PRETTY_STATE_PROTOTYPE.dup
273 end
274 if opts
275 if opts.respond_to? :to_hash
276 opts = opts.to_hash
277 elsif opts.respond_to? :to_h
278 opts = opts.to_h
279 else
280 raise TypeError, "can't convert #{opts.class} into Hash"
281 end
282 state.configure(opts)
283 end
284 state.generate(obj)
285 end
286
287 # :stopdoc:
288 # I want to deprecate these later, so I'll first be silent about them, and later delete them.
289 alias pretty_unparse pretty_generate
290 module_function :pretty_unparse
291 # :startdoc:
292
293 class << self
294 # The global default options for the JSON.load method:
295 # :max_nesting: false
296 # :allow_nan: true
297 # :quirks_mode: true
298 attr_accessor :load_default_options
299 end
300 self.load_default_options = {
301 :max_nesting => false,
302 :allow_nan => true,
303 :quirks_mode => true,
304 :create_additions => true,
305 }
306
307 # Load a ruby data structure from a JSON _source_ and return it. A source can
308 # either be a string-like object, an IO-like object, or an object responding
309 # to the read method. If _proc_ was given, it will be called with any nested
310 # Ruby object as an argument recursively in depth first order. To modify the
311 # default options pass in the optional _options_ argument as well.
312 #
313 # BEWARE: This method is meant to serialise data from trusted user input,
314 # like from your own database server or clients under your control, it could
315 # be dangerous to allow untrusted users to pass JSON sources into it. The
316 # default options for the parser can be changed via the load_default_options
317 # method.
318 #
319 # This method is part of the implementation of the load/dump interface of
320 # Marshal and YAML.
321 def load(source, proc = nil, options = {})
322 opts = load_default_options.merge options
323 if source.respond_to? :to_str
324 source = source.to_str
325 elsif source.respond_to? :to_io
326 source = source.to_io.read
327 elsif source.respond_to?(:read)
328 source = source.read
329 end
330 if opts[:quirks_mode] && (source.nil? || source.empty?)
331 source = 'null'
332 end
333 result = parse(source, opts)
334 recurse_proc(result, &proc) if proc
335 result
336 end
337
338 # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
339 def recurse_proc(result, &proc)
340 case result
341 when Array
342 result.each { |x| recurse_proc x, &proc }
343 proc.call result
344 when Hash
345 result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
346 proc.call result
347 else
348 proc.call result
349 end
350 end
351
352 alias restore load
353 module_function :restore
354
355 class << self
356 # The global default options for the JSON.dump method:
357 # :max_nesting: false
358 # :allow_nan: true
359 # :quirks_mode: true
360 attr_accessor :dump_default_options
361 end
362 self.dump_default_options = {
363 :max_nesting => false,
364 :allow_nan => true,
365 :quirks_mode => true,
366 }
367
368 # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
369 # the result.
370 #
371 # If anIO (an IO-like object or an object that responds to the write method)
372 # was given, the resulting JSON is written to it.
373 #
374 # If the number of nested arrays or objects exceeds _limit_, an ArgumentError
375 # exception is raised. This argument is similar (but not exactly the
376 # same!) to the _limit_ argument in Marshal.dump.
377 #
378 # The default options for the generator can be changed via the
379 # dump_default_options method.
380 #
381 # This method is part of the implementation of the load/dump interface of
382 # Marshal and YAML.
383 def dump(obj, anIO = nil, limit = nil)
384 if anIO and limit.nil?
385 anIO = anIO.to_io if anIO.respond_to?(:to_io)
386 unless anIO.respond_to?(:write)
387 limit = anIO
388 anIO = nil
389 end
390 end
391 opts = JSON.dump_default_options
392 opts = opts.merge(:max_nesting => limit) if limit
393 result = generate(obj, opts)
394 if anIO
395 anIO.write result
396 anIO
397 else
398 result
399 end
400 rescue JSON::NestingError
401 raise ArgumentError, "exceed depth limit"
402 end
403
404 # Swap consecutive bytes of _string_ in place.
405 def self.swap!(string) # :nodoc:
406 0.upto(string.size / 2) do |i|
407 break unless string[2 * i + 1]
408 string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
409 end
410 string
411 end
412
413 # Shortcut for iconv.
414 if ::String.method_defined?(:encode)
415 # Encodes string using Ruby's _String.encode_
416 def self.iconv(to, from, string)
417 string.encode(to, from)
418 end
419 else
420 require 'iconv'
421 # Encodes string using _iconv_ library
422 def self.iconv(to, from, string)
423 Iconv.conv(to, from, string)
424 end
425 end
426
427 if ::Object.method(:const_defined?).arity == 1
428 def self.const_defined_in?(modul, constant)
429 modul.const_defined?(constant)
430 end
431 else
432 def self.const_defined_in?(modul, constant)
433 modul.const_defined?(constant, false)
434 end
435 end
436 end
437
438 module ::Kernel
439 private
440
441 # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
442 # one line.
443 def j(*objs)
444 objs.each do |obj|
445 puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
446 end
447 nil
448 end
449
450 # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
451 # indentation and over many lines.
452 def jj(*objs)
453 objs.each do |obj|
454 puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
455 end
456 nil
457 end
458
459 # If _object_ is string-like, parse the string and return the parsed result as
460 # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data
461 # structure object and return it.
462 #
463 # The _opts_ argument is passed through to generate/parse respectively. See
464 # generate and parse for their documentation.
465 def JSON(object, *args)
466 if object.respond_to? :to_str
467 JSON.parse(object.to_str, args.first)
468 else
469 JSON.generate(object, args.first)
470 end
471 end
472 end
473
474 # Extends any Class to include _json_creatable?_ method.
475 class ::Class
476 # Returns true if this class can be used to create an instance
477 # from a serialised JSON string. The class has to implement a class
478 # method _json_create_ that expects a hash as first parameter. The hash
479 # should include the required data.
480 def json_creatable?
481 respond_to?(:json_create)
482 end
483 end
+0
-0
vendor/json/ext/.keep less more
(Empty file)
+0
-21
vendor/json/ext.rb less more
0 if ENV['SIMPLECOV_COVERAGE'].to_i == 1
1 require 'simplecov'
2 SimpleCov.start do
3 add_filter "/tests/"
4 end
5 end
6 require 'json/common'
7
8 module JSON
9 # This module holds all the modules/classes that implement JSON's
10 # functionality as C extensions.
11 module Ext
12 require 'json/ext/parser'
13 require 'json/ext/generator'
14 $DEBUG and warn "Using Ext extension for JSON."
15 JSON.parser = Parser
16 JSON.generator = Generator
17 end
18
19 JSON_LOADED = true unless defined?(::JSON::JSON_LOADED)
20 end
+0
-70
vendor/json/generic_object.rb less more
0 require 'ostruct'
1
2 module JSON
3 class GenericObject < OpenStruct
4 class << self
5 alias [] new
6
7 def json_creatable?
8 @json_creatable
9 end
10
11 attr_writer :json_creatable
12
13 def json_create(data)
14 data = data.dup
15 data.delete JSON.create_id
16 self[data]
17 end
18
19 def from_hash(object)
20 case
21 when object.respond_to?(:to_hash)
22 result = new
23 object.to_hash.each do |key, value|
24 result[key] = from_hash(value)
25 end
26 result
27 when object.respond_to?(:to_ary)
28 object.to_ary.map { |a| from_hash(a) }
29 else
30 object
31 end
32 end
33
34 def load(source, proc = nil, opts = {})
35 result = ::JSON.load(source, proc, opts.merge(:object_class => self))
36 result.nil? ? new : result
37 end
38
39 def dump(obj, *args)
40 ::JSON.dump(obj, *args)
41 end
42 end
43 self.json_creatable = false
44
45 def to_hash
46 table
47 end
48
49 def [](name)
50 table[name.to_sym]
51 end
52
53 def []=(name, value)
54 __send__ "#{name}=", value
55 end
56
57 def |(other)
58 self.class[other.to_hash.merge(to_hash)]
59 end
60
61 def as_json(*)
62 { JSON.create_id => self.class.name }.merge to_hash
63 end
64
65 def to_json(*a)
66 as_json.to_json(*a)
67 end
68 end
69 end
+0
-522
vendor/json/pure/generator.rb less more
0 module JSON
1 MAP = {
2 "\x0" => '\u0000',
3 "\x1" => '\u0001',
4 "\x2" => '\u0002',
5 "\x3" => '\u0003',
6 "\x4" => '\u0004',
7 "\x5" => '\u0005',
8 "\x6" => '\u0006',
9 "\x7" => '\u0007',
10 "\b" => '\b',
11 "\t" => '\t',
12 "\n" => '\n',
13 "\xb" => '\u000b',
14 "\f" => '\f',
15 "\r" => '\r',
16 "\xe" => '\u000e',
17 "\xf" => '\u000f',
18 "\x10" => '\u0010',
19 "\x11" => '\u0011',
20 "\x12" => '\u0012',
21 "\x13" => '\u0013',
22 "\x14" => '\u0014',
23 "\x15" => '\u0015',
24 "\x16" => '\u0016',
25 "\x17" => '\u0017',
26 "\x18" => '\u0018',
27 "\x19" => '\u0019',
28 "\x1a" => '\u001a',
29 "\x1b" => '\u001b',
30 "\x1c" => '\u001c',
31 "\x1d" => '\u001d',
32 "\x1e" => '\u001e',
33 "\x1f" => '\u001f',
34 '"' => '\"',
35 '\\' => '\\\\',
36 } # :nodoc:
37
38 # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
39 # UTF16 big endian characters as \u????, and return it.
40 if defined?(::Encoding)
41 def utf8_to_json(string) # :nodoc:
42 string = string.dup
43 string.force_encoding(::Encoding::ASCII_8BIT)
44 string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
45 string.force_encoding(::Encoding::UTF_8)
46 string
47 end
48
49 def utf8_to_json_ascii(string) # :nodoc:
50 string = string.dup
51 string.force_encoding(::Encoding::ASCII_8BIT)
52 string.gsub!(/["\\\x0-\x1f]/n) { MAP[$&] }
53 string.gsub!(/(
54 (?:
55 [\xc2-\xdf][\x80-\xbf] |
56 [\xe0-\xef][\x80-\xbf]{2} |
57 [\xf0-\xf4][\x80-\xbf]{3}
58 )+ |
59 [\x80-\xc1\xf5-\xff] # invalid
60 )/nx) { |c|
61 c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
62 s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
63 s.force_encoding(::Encoding::ASCII_8BIT)
64 s.gsub!(/.{4}/n, '\\\\u\&')
65 s.force_encoding(::Encoding::UTF_8)
66 }
67 string.force_encoding(::Encoding::UTF_8)
68 string
69 rescue => e
70 raise GeneratorError.wrap(e)
71 end
72
73 def valid_utf8?(string)
74 encoding = string.encoding
75 (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII) &&
76 string.valid_encoding?
77 end
78 module_function :valid_utf8?
79 else
80 def utf8_to_json(string) # :nodoc:
81 string.gsub(/["\\\x0-\x1f]/n) { MAP[$&] }
82 end
83
84 def utf8_to_json_ascii(string) # :nodoc:
85 string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
86 string.gsub!(/(
87 (?:
88 [\xc2-\xdf][\x80-\xbf] |
89 [\xe0-\xef][\x80-\xbf]{2} |
90 [\xf0-\xf4][\x80-\xbf]{3}
91 )+ |
92 [\x80-\xc1\xf5-\xff] # invalid
93 )/nx) { |c|
94 c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
95 s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
96 s.gsub!(/.{4}/n, '\\\\u\&')
97 }
98 string
99 rescue => e
100 raise GeneratorError.wrap(e)
101 end
102
103 def valid_utf8?(string)
104 string =~
105 /\A( [\x09\x0a\x0d\x20-\x7e] # ASCII
106 | [\xc2-\xdf][\x80-\xbf] # non-overlong 2-byte
107 | \xe0[\xa0-\xbf][\x80-\xbf] # excluding overlongs
108 | [\xe1-\xec\xee\xef][\x80-\xbf]{2} # straight 3-byte
109 | \xed[\x80-\x9f][\x80-\xbf] # excluding surrogates
110 | \xf0[\x90-\xbf][\x80-\xbf]{2} # planes 1-3
111 | [\xf1-\xf3][\x80-\xbf]{3} # planes 4-15
112 | \xf4[\x80-\x8f][\x80-\xbf]{2} # plane 16
113 )*\z/nx
114 end
115 end
116 module_function :utf8_to_json, :utf8_to_json_ascii, :valid_utf8?
117
118
119 module Pure
120 module Generator
121 # This class is used to create State instances, that are use to hold data
122 # while generating a JSON text from a Ruby data structure.
123 class State
124 # Creates a State object from _opts_, which ought to be Hash to create
125 # a new State instance configured by _opts_, something else to create
126 # an unconfigured instance. If _opts_ is a State object, it is just
127 # returned.
128 def self.from_state(opts)
129 case
130 when self === opts
131 opts
132 when opts.respond_to?(:to_hash)
133 new(opts.to_hash)
134 when opts.respond_to?(:to_h)
135 new(opts.to_h)
136 else
137 SAFE_STATE_PROTOTYPE.dup
138 end
139 end
140
141 # Instantiates a new State object, configured by _opts_.
142 #
143 # _opts_ can have the following keys:
144 #
145 # * *indent*: a string used to indent levels (default: ''),
146 # * *space*: a string that is put after, a : or , delimiter (default: ''),
147 # * *space_before*: a string that is put before a : pair delimiter (default: ''),
148 # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
149 # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
150 # * *check_circular*: is deprecated now, use the :max_nesting option instead,
151 # * *max_nesting*: sets the maximum level of data structure nesting in
152 # the generated JSON, max_nesting = 0 if no maximum should be checked.
153 # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
154 # generated, otherwise an exception is thrown, if these values are
155 # encountered. This options defaults to false.
156 # * *quirks_mode*: Enables quirks_mode for parser, that is for example
157 # generating single JSON values instead of documents is possible.
158 def initialize(opts = {})
159 @indent = ''
160 @space = ''
161 @space_before = ''
162 @object_nl = ''
163 @array_nl = ''
164 @allow_nan = false
165 @ascii_only = false
166 @quirks_mode = false
167 @buffer_initial_length = 1024
168 configure opts
169 end
170
171 # This string is used to indent levels in the JSON text.
172 attr_accessor :indent
173
174 # This string is used to insert a space between the tokens in a JSON
175 # string.
176 attr_accessor :space
177
178 # This string is used to insert a space before the ':' in JSON objects.
179 attr_accessor :space_before
180
181 # This string is put at the end of a line that holds a JSON object (or
182 # Hash).
183 attr_accessor :object_nl
184
185 # This string is put at the end of a line that holds a JSON array.
186 attr_accessor :array_nl
187
188 # This integer returns the maximum level of data structure nesting in
189 # the generated JSON, max_nesting = 0 if no maximum is checked.
190 attr_accessor :max_nesting
191
192 # If this attribute is set to true, quirks mode is enabled, otherwise
193 # it's disabled.
194 attr_accessor :quirks_mode
195
196 # :stopdoc:
197 attr_reader :buffer_initial_length
198
199 def buffer_initial_length=(length)
200 if length > 0
201 @buffer_initial_length = length
202 end
203 end
204 # :startdoc:
205
206 # This integer returns the current depth data structure nesting in the
207 # generated JSON.
208 attr_accessor :depth
209
210 def check_max_nesting # :nodoc:
211 return if @max_nesting.zero?
212 current_nesting = depth + 1
213 current_nesting > @max_nesting and
214 raise NestingError, "nesting of #{current_nesting} is too deep"
215 end
216
217 # Returns true, if circular data structures are checked,
218 # otherwise returns false.
219 def check_circular?
220 !@max_nesting.zero?
221 end
222
223 # Returns true if NaN, Infinity, and -Infinity should be considered as
224 # valid JSON and output.
225 def allow_nan?
226 @allow_nan
227 end
228
229 # Returns true, if only ASCII characters should be generated. Otherwise
230 # returns false.
231 def ascii_only?
232 @ascii_only
233 end
234
235 # Returns true, if quirks mode is enabled. Otherwise returns false.
236 def quirks_mode?
237 @quirks_mode
238 end
239
240 # Configure this State instance with the Hash _opts_, and return
241 # itself.
242 def configure(opts)
243 if opts.respond_to?(:to_hash)
244 opts = opts.to_hash
245 elsif opts.respond_to?(:to_h)
246 opts = opts.to_h
247 else
248 raise TypeError, "can't convert #{opts.class} into Hash"
249 end
250 for key, value in opts
251 instance_variable_set "@#{key}", value
252 end
253 @indent = opts[:indent] if opts.key?(:indent)
254 @space = opts[:space] if opts.key?(:space)
255 @space_before = opts[:space_before] if opts.key?(:space_before)
256 @object_nl = opts[:object_nl] if opts.key?(:object_nl)
257 @array_nl = opts[:array_nl] if opts.key?(:array_nl)
258 @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
259 @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
260 @depth = opts[:depth] || 0
261 @quirks_mode = opts[:quirks_mode] if opts.key?(:quirks_mode)
262 @buffer_initial_length ||= opts[:buffer_initial_length]
263
264 if !opts.key?(:max_nesting) # defaults to 100
265 @max_nesting = 100
266 elsif opts[:max_nesting]
267 @max_nesting = opts[:max_nesting]
268 else
269 @max_nesting = 0
270 end
271 self
272 end
273 alias merge configure
274
275 # Returns the configuration instance variables as a hash, that can be
276 # passed to the configure method.
277 def to_h
278 result = {}
279 for iv in instance_variables
280 iv = iv.to_s[1..-1]
281 result[iv.to_sym] = self[iv]
282 end
283 result
284 end
285
286 alias to_hash to_h
287
288 # Generates a valid JSON document from object +obj+ and returns the
289 # result. If no valid JSON document can be created this method raises a
290 # GeneratorError exception.
291 def generate(obj)
292 result = obj.to_json(self)
293 JSON.valid_utf8?(result) or raise GeneratorError,
294 "source sequence #{result.inspect} is illegal/malformed utf-8"
295 unless @quirks_mode
296 unless result =~ /\A\s*\[/ && result =~ /\]\s*\Z/ ||
297 result =~ /\A\s*\{/ && result =~ /\}\s*\Z/
298 then
299 raise GeneratorError, "only generation of JSON objects or arrays allowed"
300 end
301 end
302 result
303 end
304
305 # Return the value returned by method +name+.
306 def [](name)
307 if respond_to?(name)
308 __send__(name)
309 else
310 instance_variable_get("@#{name}")
311 end
312 end
313
314 def []=(name, value)
315 if respond_to?(name_writer = "#{name}=")
316 __send__ name_writer, value
317 else
318 instance_variable_set "@#{name}", value
319 end
320 end
321 end
322
323 module GeneratorMethods
324 module Object
325 # Converts this object to a string (calling #to_s), converts
326 # it to a JSON string, and returns the result. This is a fallback, if no
327 # special method #to_json was defined for some object.
328 def to_json(*) to_s.to_json end
329 end
330
331 module Hash
332 # Returns a JSON string containing a JSON object, that is unparsed from
333 # this Hash instance.
334 # _state_ is a JSON::State object, that can also be used to configure the
335 # produced JSON string output further.
336 # _depth_ is used to find out nesting depth, to indent accordingly.
337 def to_json(state = nil, *)
338 state = State.from_state(state)
339 state.check_max_nesting
340 json_transform(state)
341 end
342
343 private
344
345 def json_shift(state)
346 state.object_nl.empty? or return ''
347 state.indent * state.depth
348 end
349
350 def json_transform(state)
351 delim = ','
352 delim << state.object_nl
353 result = '{'
354 result << state.object_nl
355 depth = state.depth += 1
356 first = true
357 indent = !state.object_nl.empty?
358 each { |key,value|
359 result << delim unless first
360 result << state.indent * depth if indent
361 result << key.to_s.to_json(state)
362 result << state.space_before
363 result << ':'
364 result << state.space
365 result << value.to_json(state)
366 first = false
367 }
368 depth = state.depth -= 1
369 result << state.object_nl
370 result << state.indent * depth if indent
371 result << '}'
372 result
373 end
374 end
375
376 module Array
377 # Returns a JSON string containing a JSON array, that is unparsed from
378 # this Array instance.
379 # _state_ is a JSON::State object, that can also be used to configure the
380 # produced JSON string output further.
381 def to_json(state = nil, *)
382 state = State.from_state(state)
383 state.check_max_nesting
384 json_transform(state)
385 end
386
387 private
388
389 def json_transform(state)
390 delim = ','
391 delim << state.array_nl
392 result = '['
393 result << state.array_nl
394 depth = state.depth += 1
395 first = true
396 indent = !state.array_nl.empty?
397 each { |value|
398 result << delim unless first
399 result << state.indent * depth if indent
400 result << value.to_json(state)
401 first = false
402 }
403 depth = state.depth -= 1
404 result << state.array_nl
405 result << state.indent * depth if indent
406 result << ']'
407 end
408 end
409
410 module Integer
411 # Returns a JSON string representation for this Integer number.
412 def to_json(*) to_s end
413 end
414
415 module Float
416 # Returns a JSON string representation for this Float number.
417 def to_json(state = nil, *)
418 state = State.from_state(state)
419 case
420 when infinite?
421 if state.allow_nan?
422 to_s
423 else
424 raise GeneratorError, "#{self} not allowed in JSON"
425 end
426 when nan?
427 if state.allow_nan?
428 to_s
429 else
430 raise GeneratorError, "#{self} not allowed in JSON"
431 end
432 else
433 to_s
434 end
435 end
436 end
437
438 module String
439 if defined?(::Encoding)
440 # This string should be encoded with UTF-8 A call to this method
441 # returns a JSON string encoded with UTF16 big endian characters as
442 # \u????.
443 def to_json(state = nil, *args)
444 state = State.from_state(state)
445 if encoding == ::Encoding::UTF_8
446 string = self
447 else
448 string = encode(::Encoding::UTF_8)
449 end
450 if state.ascii_only?
451 '"' << JSON.utf8_to_json_ascii(string) << '"'
452 else
453 '"' << JSON.utf8_to_json(string) << '"'
454 end
455 end
456 else
457 # This string should be encoded with UTF-8 A call to this method
458 # returns a JSON string encoded with UTF16 big endian characters as
459 # \u????.
460 def to_json(state = nil, *args)
461 state = State.from_state(state)
462 if state.ascii_only?
463 '"' << JSON.utf8_to_json_ascii(self) << '"'
464 else
465 '"' << JSON.utf8_to_json(self) << '"'
466 end
467 end
468 end
469
470 # Module that holds the extinding methods if, the String module is
471 # included.
472 module Extend
473 # Raw Strings are JSON Objects (the raw bytes are stored in an
474 # array for the key "raw"). The Ruby String can be created by this
475 # module method.
476 def json_create(o)
477 o['raw'].pack('C*')
478 end
479 end
480
481 # Extends _modul_ with the String::Extend module.
482 def self.included(modul)
483 modul.extend Extend
484 end
485
486 # This method creates a raw object hash, that can be nested into
487 # other data structures and will be unparsed as a raw string. This
488 # method should be used, if you want to convert raw strings to JSON
489 # instead of UTF-8 strings, e. g. binary data.
490 def to_json_raw_object
491 {
492 JSON.create_id => self.class.name,
493 'raw' => self.unpack('C*'),
494 }
495 end
496
497 # This method creates a JSON text from the result of
498 # a call to to_json_raw_object of this String.
499 def to_json_raw(*args)
500 to_json_raw_object.to_json(*args)
501 end
502 end
503
504 module TrueClass
505 # Returns a JSON string for true: 'true'.
506 def to_json(*) 'true' end
507 end
508
509 module FalseClass
510 # Returns a JSON string for false: 'false'.
511 def to_json(*) 'false' end
512 end
513
514 module NilClass
515 # Returns a JSON string for nil: 'null'.
516 def to_json(*) 'null' end
517 end
518 end
519 end
520 end
521 end
+0
-359
vendor/json/pure/parser.rb less more
0 require 'strscan'
1
2 module JSON
3 module Pure
4 # This class implements the JSON parser that is used to parse a JSON string
5 # into a Ruby data structure.
6 class Parser < StringScanner
7 STRING = /" ((?:[^\x0-\x1f"\\] |
8 # escaped special characters:
9 \\["\\\/bfnrt] |
10 \\u[0-9a-fA-F]{4} |
11 # match all but escaped special characters:
12 \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
13 "/nx
14 INTEGER = /(-?0|-?[1-9]\d*)/
15 FLOAT = /(-?
16 (?:0|[1-9]\d*)
17 (?:
18 \.\d+(?i:e[+-]?\d+) |
19 \.\d+ |
20 (?i:e[+-]?\d+)
21 )
22 )/x
23 NAN = /NaN/
24 INFINITY = /Infinity/
25 MINUS_INFINITY = /-Infinity/
26 OBJECT_OPEN = /\{/
27 OBJECT_CLOSE = /\}/
28 ARRAY_OPEN = /\[/
29 ARRAY_CLOSE = /\]/
30 PAIR_DELIMITER = /:/
31 COLLECTION_DELIMITER = /,/
32 TRUE = /true/
33 FALSE = /false/
34 NULL = /null/
35 IGNORE = %r(
36 (?:
37 //[^\n\r]*[\n\r]| # line comments
38 /\* # c-style comments
39 (?:
40 [^*/]| # normal chars
41 /[^*]| # slashes that do not start a nested comment
42 \*[^/]| # asterisks that do not end this comment
43 /(?=\*/) # single slash before this comment's end
44 )*
45 \*/ # the End of this comment
46 |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
47 )+
48 )mx
49
50 UNPARSED = Object.new
51
52 # Creates a new JSON::Pure::Parser instance for the string _source_.
53 #
54 # It will be configured by the _opts_ hash. _opts_ can have the following
55 # keys:
56 # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
57 # structures. Disable depth checking with :max_nesting => false|nil|0,
58 # it defaults to 100.
59 # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
60 # defiance of RFC 4627 to be parsed by the Parser. This option defaults
61 # to false.
62 # * *symbolize_names*: If set to true, returns symbols for the names
63 # (keys) in a JSON object. Otherwise strings are returned, which is also
64 # the default.
65 # * *create_additions*: If set to true, the Parser creates
66 # additions when if a matching class and create_id was found. This
67 # option defaults to false.
68 # * *object_class*: Defaults to Hash
69 # * *array_class*: Defaults to Array
70 # * *quirks_mode*: Enables quirks_mode for parser, that is for example
71 # parsing single JSON values instead of documents is possible.
72 def initialize(source, opts = {})
73 opts ||= {}
74 unless @quirks_mode = opts[:quirks_mode]
75 source = convert_encoding source
76 end
77 super source
78 if !opts.key?(:max_nesting) # defaults to 100
79 @max_nesting = 100
80 elsif opts[:max_nesting]
81 @max_nesting = opts[:max_nesting]
82 else
83 @max_nesting = 0
84 end
85 @allow_nan = !!opts[:allow_nan]
86 @symbolize_names = !!opts[:symbolize_names]
87 if opts.key?(:create_additions)
88 @create_additions = !!opts[:create_additions]
89 else
90 @create_additions = false
91 end
92 @create_id = @create_additions ? JSON.create_id : nil
93 @object_class = opts[:object_class] || Hash
94 @array_class = opts[:array_class] || Array
95 @match_string = opts[:match_string]
96 end
97
98 alias source string
99
100 def quirks_mode?
101 !!@quirks_mode
102 end
103
104 def reset
105 super
106 @current_nesting = 0
107 end
108
109 # Parses the current JSON string _source_ and returns the complete data
110 # structure as a result.
111 def parse
112 reset
113 obj = nil
114 if @quirks_mode
115 while !eos? && skip(IGNORE)
116 end
117 if eos?
118 raise ParserError, "source did not contain any JSON!"
119 else
120 obj = parse_value
121 obj == UNPARSED and raise ParserError, "source did not contain any JSON!"
122 end
123 else
124 until eos?
125 case
126 when scan(OBJECT_OPEN)
127 obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
128 @current_nesting = 1
129 obj = parse_object
130 when scan(ARRAY_OPEN)
131 obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
132 @current_nesting = 1
133 obj = parse_array
134 when skip(IGNORE)
135 ;
136 else
137 raise ParserError, "source '#{peek(20)}' not in JSON!"
138 end
139 end
140 obj or raise ParserError, "source did not contain any JSON!"
141 end
142 obj
143 end
144
145 private
146
147 def convert_encoding(source)
148 if source.respond_to?(:to_str)
149 source = source.to_str
150 else
151 raise TypeError, "#{source.inspect} is not like a string"
152 end
153 if defined?(::Encoding)
154 if source.encoding == ::Encoding::ASCII_8BIT
155 b = source[0, 4].bytes.to_a
156 source =
157 case
158 when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
159 source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
160 when b.size >= 4 && b[0] == 0 && b[2] == 0
161 source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
162 when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
163 source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
164 when b.size >= 4 && b[1] == 0 && b[3] == 0
165 source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
166 else
167 source.dup
168 end
169 else
170 source = source.encode(::Encoding::UTF_8)
171 end
172 source.force_encoding(::Encoding::ASCII_8BIT)
173 else
174 b = source
175 source =
176 case
177 when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
178 JSON.iconv('utf-8', 'utf-32be', b)
179 when b.size >= 4 && b[0] == 0 && b[2] == 0
180 JSON.iconv('utf-8', 'utf-16be', b)
181 when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
182 JSON.iconv('utf-8', 'utf-32le', b)
183 when b.size >= 4 && b[1] == 0 && b[3] == 0
184 JSON.iconv('utf-8', 'utf-16le', b)
185 else
186 b
187 end
188 end
189 source
190 end
191
192 # Unescape characters in strings.
193 UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
194 UNESCAPE_MAP.update({
195 ?" => '"',
196 ?\\ => '\\',
197 ?/ => '/',
198 ?b => "\b",
199 ?f => "\f",
200 ?n => "\n",
201 ?r => "\r",
202 ?t => "\t",
203 ?u => nil,
204 })
205
206 EMPTY_8BIT_STRING = ''
207 if ::String.method_defined?(:encode)
208 EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
209 end
210
211 def parse_string
212 if scan(STRING)
213 return '' if self[1].empty?
214 string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
215 if u = UNESCAPE_MAP[$&[1]]
216 u
217 else # \uXXXX
218 bytes = EMPTY_8BIT_STRING.dup
219 i = 0
220 while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
221 bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
222 i += 1
223 end
224 JSON.iconv('utf-8', 'utf-16be', bytes)
225 end
226 end
227 if string.respond_to?(:force_encoding)
228 string.force_encoding(::Encoding::UTF_8)
229 end
230 if @create_additions and @match_string
231 for (regexp, klass) in @match_string
232 klass.json_creatable? or next
233 string =~ regexp and return klass.json_create(string)
234 end
235 end
236 string
237 else
238 UNPARSED
239 end
240 rescue => e
241 raise ParserError, "Caught #{e.class} at '#{peek(20)}': #{e}"
242 end
243
244 def parse_value
245 case
246 when scan(FLOAT)
247 Float(self[1])
248 when scan(INTEGER)
249 Integer(self[1])
250 when scan(TRUE)
251 true
252 when scan(FALSE)
253 false
254 when scan(NULL)
255 nil
256 when (string = parse_string) != UNPARSED
257 string
258 when scan(ARRAY_OPEN)
259 @current_nesting += 1
260 ary = parse_array
261 @current_nesting -= 1
262 ary
263 when scan(OBJECT_OPEN)
264 @current_nesting += 1
265 obj = parse_object
266 @current_nesting -= 1
267 obj
268 when @allow_nan && scan(NAN)
269 NaN
270 when @allow_nan && scan(INFINITY)
271 Infinity
272 when @allow_nan && scan(MINUS_INFINITY)
273 MinusInfinity
274 else
275 UNPARSED
276 end
277 end
278
279 def parse_array
280 raise NestingError, "nesting of #@current_nesting is too deep" if
281 @max_nesting.nonzero? && @current_nesting > @max_nesting
282 result = @array_class.new
283 delim = false
284 until eos?
285 case
286 when (value = parse_value) != UNPARSED
287 delim = false
288 result << value
289 skip(IGNORE)
290 if scan(COLLECTION_DELIMITER)
291 delim = true
292 elsif match?(ARRAY_CLOSE)
293 ;
294 else
295 raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
296 end
297 when scan(ARRAY_CLOSE)
298 if delim
299 raise ParserError, "expected next element in array at '#{peek(20)}'!"
300 end
301 break
302 when skip(IGNORE)
303 ;
304 else
305 raise ParserError, "unexpected token in array at '#{peek(20)}'!"
306 end
307 end
308 result
309 end
310
311 def parse_object
312 raise NestingError, "nesting of #@current_nesting is too deep" if
313 @max_nesting.nonzero? && @current_nesting > @max_nesting
314 result = @object_class.new
315 delim = false
316 until eos?
317 case
318 when (string = parse_string) != UNPARSED
319 skip(IGNORE)
320 unless scan(PAIR_DELIMITER)
321 raise ParserError, "expected ':' in object at '#{peek(20)}'!"
322 end
323 skip(IGNORE)
324 unless (value = parse_value).equal? UNPARSED
325 result[@symbolize_names ? string.to_sym : string] = value
326 delim = false
327 skip(IGNORE)
328 if scan(COLLECTION_DELIMITER)
329 delim = true
330 elsif match?(OBJECT_CLOSE)
331 ;
332 else
333 raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
334 end
335 else
336 raise ParserError, "expected value in object at '#{peek(20)}'!"
337 end
338 when scan(OBJECT_CLOSE)
339 if delim
340 raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
341 end
342 if @create_additions and klassname = result[@create_id]
343 klass = JSON.deep_const_get klassname
344 break unless klass and klass.json_creatable?
345 result = klass.json_create(result)
346 end
347 break
348 when skip(IGNORE)
349 ;
350 else
351 raise ParserError, "unexpected token in object at '#{peek(20)}'!"
352 end
353 end
354 result
355 end
356 end
357 end
358 end
+0
-21
vendor/json/pure.rb less more
0 if ENV['SIMPLECOV_COVERAGE'].to_i == 1
1 require 'simplecov'
2 SimpleCov.start do
3 add_filter "/tests/"
4 end
5 end
6 require 'json/common'
7 require 'json/pure/parser'
8 require 'json/pure/generator'
9
10 module JSON
11 # This module holds all the modules/classes that implement JSON's
12 # functionality in pure ruby.
13 module Pure
14 $DEBUG and warn "Using Pure library for JSON."
15 JSON.parser = Parser
16 JSON.generator = Generator
17 end
18
19 JSON_LOADED = true unless defined?(::JSON::JSON_LOADED)
20 end
+0
-8
vendor/json/version.rb less more
0 module JSON
1 # JSON version
2 VERSION = '1.8.2'
3 VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
4 VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
5 VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
6 VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
7 end
+0
-62
vendor/json.rb less more
0 require 'json/common'
1
2 ##
3 # = JavaScript Object Notation (JSON)
4 #
5 # JSON is a lightweight data-interchange format. It is easy for us
6 # humans to read and write. Plus, equally simple for machines to generate or parse.
7 # JSON is completely language agnostic, making it the ideal interchange format.
8 #
9 # Built on two universally available structures:
10 # 1. A collection of name/value pairs. Often referred to as an _object_, hash table, record, struct, keyed list, or associative array.
11 # 2. An ordered list of values. More commonly called an _array_, vector, sequence or list.
12 #
13 # To read more about JSON visit: http://json.org
14 #
15 # == Parsing JSON
16 #
17 # To parse a JSON string received by another application or generated within
18 # your existing application:
19 #
20 # require 'json'
21 #
22 # my_hash = JSON.parse('{"hello": "goodbye"}')
23 # puts my_hash["hello"] => "goodbye"
24 #
25 # Notice the extra quotes <tt>''</tt> around the hash notation. Ruby expects
26 # the argument to be a string and can't convert objects like a hash or array.
27 #
28 # Ruby converts your string into a hash
29 #
30 # == Generating JSON
31 #
32 # Creating a JSON string for communication or serialization is
33 # just as simple.
34 #
35 # require 'json'
36 #
37 # my_hash = {:hello => "goodbye"}
38 # puts JSON.generate(my_hash) => "{\"hello\":\"goodbye\"}"
39 #
40 # Or an alternative way:
41 #
42 # require 'json'
43 # puts {:hello => "goodbye"}.to_json => "{\"hello\":\"goodbye\"}"
44 #
45 # <tt>JSON.generate</tt> only allows objects or arrays to be converted
46 # to JSON syntax. <tt>to_json</tt>, however, accepts many Ruby classes
47 # even though it acts only as a method for serialization:
48 #
49 # require 'json'
50 #
51 # 1.to_json => "1"
52 #
53 module JSON
54 require 'json/version'
55
56 begin
57 require 'json/ext'
58 rescue LoadError
59 require 'json/pure'
60 end
61 end
vendor/locale/data/languages.tab.gz less more
Binary diff not shown
vendor/locale/data/regions.tab.gz less more
Binary diff not shown
+0
-150
vendor/locale/driver/cgi.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2002-2008 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "locale/driver"
22
23 module Locale
24 # Locale::Driver module for CGI.
25 # Detect the user locales and the charset from CGI parameters.
26 # This is a low-level class. Application shouldn't use this directly.
27 module Driver
28 module CGI
29 $stderr.puts self.name + " is loaded." if $DEBUG
30
31 module_function
32 # Gets required locales from CGI parameters. (Based on RFC2616)
33 #
34 # Returns: An Array of Locale::Tag's subclasses
35 # (QUERY_STRING "lang" > COOKIE "lang" > HTTP_ACCEPT_LANGUAGE > "en")
36 #
37 def locales
38 req = Thread.current[:current_request]
39 return nil unless req
40
41 locales = []
42
43 # QUERY_STRING "lang"
44 if langs = req[:query_langs]
45 langs.each do |lang|
46 locales << Locale::Tag.parse(lang)
47 end
48 end
49
50 unless locales.size > 0
51 # COOKIE "lang"
52 if langs = req[:cookie_langs]
53 langs.each do |lang|
54 locales << Locale::Tag.parse(lang) if lang.size > 0
55 end
56 end
57 end
58
59 unless locales.size > 0
60 # HTTP_ACCEPT_LANGUAGE
61 if lang = req[:accept_language] and lang.size > 0
62 # 10.0 is for ruby-1.8.6 which have the bug of str.to_f.
63 # Normally, this should be 1.0.
64 locales += lang.gsub(/\s/, "").split(/,/).map{|v| v.split(";q=")}.map{|j| [j[0], j[1] ? j[1].to_f : 10.0]}.sort{|a,b| -(a[1] <=> b[1])}.map{|v| Locale::Tag.parse(v[0])}
65 end
66 end
67
68 locales.size > 0 ? Locale::TagList.new(locales.uniq) : nil
69 end
70
71 # Gets the charset from CGI parameters. (Based on RFC2616)
72 # * Returns: the charset (HTTP_ACCEPT_CHARSET or nil).
73 def charset
74 req = Thread.current[:current_request]
75 return nil unless req
76
77 charsets = req[:accept_charset]
78 if charsets and charsets.size > 0
79 num = charsets.index(',')
80 charset = num ? charsets[0, num] : charsets
81 charset = nil if charset == "*"
82 else
83 charset = nil
84 end
85 charset
86 end
87
88 # Set a request.
89 #
90 # * query_langs: An Array of QUERY_STRING value "lang".
91 # * cookie_langs: An Array of cookie value "lang".
92 # * accept_language: The value of HTTP_ACCEPT_LANGUAGE
93 # * accept_charset: The value of HTTP_ACCEPT_CHARSET
94 def set_request(query_langs, cookie_langs, accept_language, accept_charset)
95 Locale.clear
96 Thread.current[:current_request] = {
97 :query_langs => query_langs,
98 :cookie_langs => cookie_langs,
99 :accept_language => accept_language,
100 :accept_charset => accept_charset
101 }
102 self
103 end
104
105 # Clear the current request.
106 def clear_current_request
107 Thread.current[:current_request] = nil
108 end
109 end
110
111 MODULES[:cgi] = CGI
112 end
113
114
115 module_function
116 # Sets a request values for lang/charset.
117 #
118 # * query_langs: An Array of QUERY_STRING value "lang".
119 # * cookie_langs: An Array of cookie value "lang".
120 # * accept_language: The value of HTTP_ACCEPT_LANGUAGE
121 # * accept_charset: The value of HTTP_ACCEPT_CHARSET
122 def set_request(query_langs, cookie_langs, accept_language, accept_charset)
123 driver_module.set_request(query_langs, cookie_langs, accept_language, accept_charset)
124 self
125 end
126
127 # Sets a CGI object. This is the convenient function of set_request().
128 #
129 # This method is appeared when Locale.init(:driver => :cgi) is called.
130 #
131 # * cgi: CGI object
132 # * Returns: self
133 def set_cgi(cgi)
134 set_request(cgi.params["lang"], cgi.cookies["lang"],
135 cgi.accept_language, cgi.accept_charset)
136 self
137 end
138
139 # Sets a CGI object.This is the convenient function of set_request().
140 #
141 # This method is appeared when Locale.init(:driver => :cgi) is called.
142 #
143 # * cgi: CGI object
144 # * Returns: cgi
145 def cgi=(cgi)
146 set_cgi(cgi)
147 cgi
148 end
149 end
+0
-97
vendor/locale/driver/env.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2012 Hleb Valoshka
4 # Copyright (C) 2008 Masao Mutoh
5 #
6 # Original: Ruby-GetText-Package-1.92.0.
7 # License: Ruby's or LGPL
8 #
9 # This library is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU Lesser General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 require 'locale/tag'
23 require 'locale/taglist'
24 require "locale/driver"
25
26 module Locale
27 module Driver
28 # Locale::Driver::Env module.
29 # Detect the user locales and the charset.
30 # All drivers(except CGI) refer environment variables first and use it
31 # as the locale if it's defined.
32 # This is a low-level module. Application shouldn't use this directly.
33 module Env
34 module_function
35
36 # Gets the locale from environment variable.
37 # Priority order except charset is LC_ALL > LC_MESSAGES > LANG.
38 # Priority order for charset is LC_ALL > LC_CTYPE > LANG.
39 # Returns: the locale as Locale::Tag::Posix.
40 def locale
41 lc_all = Private.parse(ENV["LC_ALL"])
42 return lc_all if lc_all
43
44 lc_messages = Private.parse(ENV["LC_MESSAGES"])
45 lang = Private.parse(ENV["LANG"])
46
47 tag = lc_messages || lang
48 return nil if tag.nil?
49
50 lc_ctype = Private.parse(ENV["LC_CTYPE"])
51 tag.charset = lc_ctype.charset if lc_ctype
52
53 tag
54 end
55
56 # Gets the locales from environment variables. (LANGUAGE > LC_ALL > LC_MESSAGES > LANG)
57 # * Returns: an Array of the locale as Locale::Tag::Posix or nil.
58 def locales
59 locales = ENV["LANGUAGE"]
60 if (locales != nil and locales.size > 0)
61 locs = locales.split(/:/).collect{|v| Locale::Tag::Posix.parse(v)}.compact
62 if locs.size > 0
63 return Locale::TagList.new(locs)
64 end
65 elsif (loc = locale)
66 return Locale::TagList.new([loc])
67 end
68 nil
69 end
70
71 # Gets the charset from environment variables
72 # (LC_ALL > LC_CTYPE > LANG) or return nil.
73 # * Returns: the system charset.
74 def charset # :nodoc:
75 [ENV["LC_ALL"], ENV["LC_CTYPE"], ENV["LANG"]].each do |env|
76 tag = Private.parse(env)
77 next if tag.nil?
78 return tag.charset
79 end
80 nil
81 end
82
83 module Private
84 module_function
85 def parse(env_value)
86 return nil if env_value.nil?
87 return nil if env_value.empty?
88 Locale::Tag::Posix.parse(env_value)
89 end
90 end
91 end
92
93 MODULES[:env] = Env
94 end
95 end
96
+0
-62
vendor/locale/driver/jruby.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2007-2008 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require 'java'
22
23 require "locale/driver/env"
24
25 module Locale
26 module Driver
27 # Locale::Driver::JRuby module for JRuby
28 # Detect the user locales and the charset.
29 # This is a low-level class. Application shouldn't use this directly.
30 module JRuby
31 $stderr.puts self.name + " is loaded." if $DEBUG
32
33 module_function
34 def locales #:nodoc:
35 locales = ::Locale::Driver::Env.locales
36 unless locales
37 locale = java.util.Locale.getDefault
38 variant = locale.getVariant
39 variants = []
40 if variant != nil and variant.size > 0
41 variants = [variant]
42 end
43 locales = TagList.new([Locale::Tag::Common.new(locale.getLanguage, nil,
44 locale.getCountry,
45 variants)])
46 end
47 locales
48 end
49
50 def charset #:nodoc:
51 charset = ::Locale::Driver::Env.charset
52 unless charset
53 charset = java.nio.charset.Charset.defaultCharset.name
54 end
55 charset
56 end
57 end
58
59 MODULES[:jruby] = JRuby
60 end
61 end
+0
-58
vendor/locale/driver/posix.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2002-2008 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "locale/driver/env"
22
23 module Locale
24 # Locale::Driver::Posix module for Posix OS (Unix)
25 # Detect the user locales and the charset.
26 # This is a low-level class. Application shouldn't use this directly.
27 module Driver
28 module Posix
29 $stderr.puts self.name + " is loaded." if $DEBUG
30
31 module_function
32 # Gets the locales from environment variables. (LANGUAGE > LC_ALL > LC_MESSAGES > LANG)
33 # Only LANGUAGE accept plural languages such as "nl_BE;
34 # * Returns: an Array of the locale as Locale::Tag::Posix or nil.
35 def locales
36 ::Locale::Driver::Env.locales
37 end
38
39 # Gets the charset from environment variable or the result of
40 # "locale charmap" or nil.
41 # * Returns: the system charset.
42 def charset
43 charset = ::Locale::Driver::Env.charset
44 unless charset
45 charset = `locale charmap`.strip
46 unless $? && $?.success?
47 charset = nil
48 end
49 end
50 charset
51 end
52 end
53
54 MODULES[:posix] = Posix
55 end
56 end
57
+0
-94
vendor/locale/driver/win32.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2002-2010 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "locale/driver/env"
22 require "locale/driver/win32_table"
23
24 require "fiddle/import"
25
26 module Locale
27 # Locale::Driver::Win32 module for win32.
28 # Detect the user locales and the charset.
29 # This is a low-level class. Application shouldn't use this directly.
30 module Driver
31 module Win32
32 module Kernel32
33 extend Fiddle::Importer
34 dlload "kernel32.dll"
35 extern "int GetThreadLocale()"
36 end
37
38 include Win32Table
39
40 $stderr.puts self.name + " is loaded." if $DEBUG
41
42 @@current_locale_id = nil
43
44 module_function
45
46 # Gets the Win32 charset of the locale.
47 def charset
48 charset = ::Locale::Driver::Env.charset
49 unless charset
50 if locales
51 tag = locales[0].to_rfc.to_s
52 loc = LocaleTable.find{|v| v[1] == tag}
53 loc = LocaleTable.find{|v| v[1] =~ /^#{locales[0].language}/} unless loc
54 charset = loc ? loc[2] : nil
55 else
56 charset = "CP1252"
57 end
58 end
59 charset
60 end
61
62 def thread_locale_id #:nodoc:
63 if @@current_locale_id
64 @@current_locale_id
65 else
66 Kernel32.GetThreadLocale
67 end
68 end
69
70 def set_thread_locale_id(lcid) #:nodoc:
71 # for testing.
72 @@current_locale_id = lcid
73 end
74
75 def locales #:nodoc:
76 locales = ::Locale::Driver::Env.locales
77 unless locales
78 lang = LocaleTable.assoc(thread_locale_id)
79 if lang
80 ret = Locale::Tag::Common.parse(lang[1])
81 locales = Locale::TagList.new([ret])
82 else
83 locales = nil
84 end
85 end
86 locales
87 end
88 end
89
90 MODULES[:win32] = Win32
91 end
92 end
93
+0
-298
vendor/locale/driver/win32_table.rb less more
0 =begin
1 win32_table.rb - Locale table for win32
2
3 Copyright (C) 2008 Masao Mutoh <mutomasa at gmail.com>
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 Original: Ruby-GetText-Package-1.92.0.
9
10 $Id: win32_table.rb 27 2008-12-03 15:06:50Z mutoh $
11 =end
12
13 module Locale
14 module Driver
15 module Win32Table
16
17 #http://msdn.microsoft.com/ja-jp/goglobal/bb896001(en-us).aspx
18
19 #LangID, locale name, code page
20 LocaleTable = [
21 [0x0036,"af","CP1252"],
22 [0x0436,"af-ZA","CP1252"],
23 [0x001C,"sq","CP1250"],
24 [0x041C,"sq-AL","CP1250"],
25 [0x0484,"gsw-FR","CP1252"],
26 [0x045E,"am-ET","UNICODE"],
27 [0x0001,"ar","CP1256"],
28 [0x1401,"ar-DZ","CP1256"],
29 [0x3C01,"ar-BH","CP1256"],
30 [0x0C01,"ar-EG","CP1256"],
31 [0x0801,"ar-IQ","CP1256"],
32 [0x2C01,"ar-JO","CP1256"],
33 [0x3401,"ar-KW","CP1256"],
34 [0x3001,"ar-LB","CP1256"],
35 [0x1001,"ar-LY","CP1256"],
36 [0x1801,"ar-MA","CP1256"],
37 [0x2001,"ar-OM","CP1256"],
38 [0x4001,"ar-QA","CP1256"],
39 [0x0401,"ar-SA","CP1256"],
40 [0x2801,"ar-SY","CP1256"],
41 [0x1C01,"ar-TN","CP1256"],
42 [0x3801,"ar-AE","CP1256"],
43 [0x2401,"ar-YE","CP1256"],
44 [0x002B,"hy","UNICODE"],
45 [0x042B,"hy-AM","UNICODE"],
46 [0x044D,"as-IN","UNICODE"],
47 [0x002C,"az","CP1254"],
48 [0x082C,"az-Cyrl-AZ","CP1251"],
49 [0x042C,"az-Latn-AZ","CP1254"],
50 [0x046D,"ba-RU","CP1251"],
51 [0x002D,"eu","CP1252"],
52 [0x042D,"eu-ES","CP1252"],
53 [0x0023,"be","CP1251"],
54 [0x0423,"be-BY","CP1251"],
55 [0x0845,"bn-BD","UNICODE"],
56 [0x0445,"bn-IN","UNICODE"],
57 [0x201A,"bs-Cyrl-BA","CP1251"],
58 [0x141A,"bs-Latn-BA","CP1250"],
59 [0x047E,"br-FR","CP1252"],
60 [0x0002,"bg","CP1251"],
61 [0x0402,"bg-BG","CP1251"],
62 [0x0003,"ca","CP1252"],
63 [0x0403,"ca-ES","CP1252"],
64 [0x0C04,"zh-HK","CP950"],
65 [0x1404,"zh-MO","CP950"],
66 [0x0804,"zh-CN","CP936"],
67 [0x0004,"zh-Hans","CP936"],
68 [0x1004,"zh-SG","CP936"],
69 [0x0404,"zh-TW","CP950"],
70 [0x7C04,"zh-Hant","CP950"],
71 [0x0483,"co-FR","CP1252"],
72 [0x001A,"hr","CP1250"],
73 [0x041A,"hr-HR","CP1250"],
74 [0x101A,"hr-BA","CP1250"],
75 [0x0005,"cs","CP1250"],
76 [0x0405,"cs-CZ","CP1250"],
77 [0x0006,"da","CP1252"],
78 [0x0406,"da-DK","CP1252"],
79 [0x048C,"prs-AF","CP1256"],
80 [0x0065,"div","UNICODE"],
81 [0x0465,"div-MV","UNICODE"],
82 [0x0013,"nl","CP1252"],
83 [0x0813,"nl-BE","CP1252"],
84 [0x0413,"nl-NL","CP1252"],
85 [0x0009,"en","CP1252"],
86 [0x0C09,"en-AU","CP1252"],
87 [0x2809,"en-BZ","CP1252"],
88 [0x1009,"en-CA","CP1252"],
89 [0x2409,"en-029","CP1252"],
90 [0x4009,"en-IN","CP1252"],
91 [0x1809,"en-IE","CP1252"],
92 [0x2009,"en-JM","CP1252"],
93 [0x4409,"en-MY","CP1252"],
94 [0x1409,"en-NZ","CP1252"],
95 [0x3409,"en-PH","CP1252"],
96 [0x4809,"en-SG","CP1252"],
97 [0x1C09,"en-ZA","CP1252"],
98 [0x2C09,"en-TT","CP1252"],
99 [0x0809,"en-GB","CP1252"],
100 [0x0409,"en-US","CP1252"],
101 [0x3009,"en-ZW","CP1252"],
102 [0x0025,"et","CP1257"],
103 [0x0425,"et-EE","CP1257"],
104 [0x0038,"fo","CP1252"],
105 [0x0438,"fo-FO","CP1252"],
106 [0x0464,"fil-PH","CP1252"],
107 [0x000B,"fi","CP1252"],
108 [0x040B,"fi-FI","CP1252"],
109 [0x000C,"fr","CP1252"],
110 [0x080C,"fr-BE","CP1252"],
111 [0x0C0C,"fr-CA","CP1252"],
112 [0x040C,"fr-FR","CP1252"],
113 [0x140C,"fr-LU","CP1252"],
114 [0x180C,"fr-MC","CP1252"],
115 [0x100C,"fr-CH","CP1252"],
116 [0x0462,"fy-NL","CP1252"],
117 [0x0056,"gl","CP1252"],
118 [0x0456,"gl-ES","CP1252"],
119 [0x0037,"ka","UNICODE"],
120 [0x0437,"ka-GE","UNICODE"],
121 [0x0007,"de","CP1252"],
122 [0x0C07,"de-AT","CP1252"],
123 [0x0407,"de-DE","CP1252"],
124 [0x1407,"de-LI","CP1252"],
125 [0x1007,"de-LU","CP1252"],
126 [0x0807,"de-CH","CP1252"],
127 [0x0008,"el","CP1253"],
128 [0x0408,"el-GR","CP1253"],
129 [0x046F,"kl-GL","CP1252"],
130 [0x0047,"gu","UNICODE"],
131 [0x0447,"gu-IN","UNICODE"],
132 [0x0468,"ha-Latn-NG","CP1252"],
133 [0x000D,"he","CP1255"],
134 [0x040D,"he-IL","CP1255"],
135 [0x0039,"hi","UNICODE"],
136 [0x0439,"hi-IN","UNICODE"],
137 [0x000E,"hu","CP1250"],
138 [0x040E,"hu-HU","CP1250"],
139 [0x000F,"is","CP1252"],
140 [0x040F,"is-IS","CP1252"],
141 [0x0470,"ig-NG","CP1252"],
142 [0x0021,"id","CP1252"],
143 [0x0421,"id-ID","CP1252"],
144 [0x085D,"iu-Latn-CA","CP1252"],
145 [0x045D,"iu-Cans-CA","UNICODE"],
146 [0x083C,"ga-IE","CP1252"],
147 [0x0434,"xh-ZA","CP1252"],
148 [0x0435,"zu-ZA","CP1252"],
149 [0x0010,"it","CP1252"],
150 [0x0410,"it-IT","CP1252"],
151 [0x0810,"it-CH","CP1252"],
152 [0x0011,"ja","CP932"],
153 [0x0411,"ja-JP","CP932"],
154 [0x004B,"kn","UNICODE"],
155 [0x044B,"kn-IN","UNICODE"],
156 [0x003F,"kk","CP1251"],
157 [0x043F,"kk-KZ","CP1251"],
158 [0x0453,"km-KH","UNICODE"],
159 [0x0486,"qut-GT","CP1252"],
160 [0x0487,"rw-RW","CP1252"],
161 [0x0041,"sw","CP1252"],
162 [0x0441,"sw-KE","CP1252"],
163 [0x0057,"kok","UNICODE"],
164 [0x0457,"kok-IN","UNICODE"],
165 [0x0012,"ko","CP949"],
166 [0x0412,"ko-KR","CP949"],
167 [0x0040,"ky","CP1251"],
168 [0x0440,"ky-KG","CP1251"],
169 [0x0454,"lo-LA","UNICODE"],
170 [0x0026,"lv","CP1257"],
171 [0x0426,"lv-LV","CP1257"],
172 [0x0027,"lt","CP1257"],
173 [0x0427,"lt-LT","CP1257"],
174 [0x082E,"wee-DE","CP1252"],
175 [0x046E,"lb-LU","CP1252"],
176 [0x002F,"mk","CP1251"],
177 [0x042F,"mk-MK","CP1251"],
178 [0x003E,"ms","CP1252"],
179 [0x083E,"ms-BN","CP1252"],
180 [0x043E,"ms-MY","CP1252"],
181 [0x044C,"ml-IN","UNICODE"],
182 [0x043A,"mt-MT","UNICODE"],
183 [0x0481,"mi-NZ","UNICODE"],
184 [0x047A,"arn-CL","CP1252"],
185 [0x004E,"mr","UNICODE"],
186 [0x044E,"mr-IN","UNICODE"],
187 [0x047C,"moh-CA","CP1252"],
188 [0x0050,"mn","CP1251"],
189 [0x0450,"mn-MN","CP1251"],
190 [0x0850,"mn-Mong-CN","UNICODE"],
191 [0x0461,"ne-NP","UNICODE"],
192 [0x0014,"no","CP1252"],
193 [0x0414,"nb-NO","CP1252"],
194 [0x0814,"nn-NO","CP1252"],
195 [0x0482,"oc-FR","CP1252"],
196 [0x0448,"or-IN","UNICODE"],
197 [0x0463,"ps-AF","UNICODE"],
198 [0x0029,"fa","CP1256"],
199 [0x0429,"fa-IR","CP1256"],
200 [0x0015,"pl","CP1250"],
201 [0x0415,"pl-PL","CP1250"],
202 [0x0016,"pt","CP1252"],
203 [0x0416,"pt-BR","CP1252"],
204 [0x0816,"pt-PT","CP1252"],
205 [0x0046,"pa","UNICODE"],
206 [0x0446,"pa-IN","UNICODE"],
207 [0x046B,"quz-BO","CP1252"],
208 [0x086B,"quz-EC","CP1252"],
209 [0x0C6B,"quz-PE","CP1252"],
210 [0x0018,"ro","CP1250"],
211 [0x0418,"ro-RO","CP1250"],
212 [0x0417,"rm-CH","CP1252"],
213 [0x0019,"ru","CP1251"],
214 [0x0419,"ru-RU","CP1251"],
215 [0x243B,"smn-FI","CP1252"],
216 [0x103B,"smj-NO","CP1252"],
217 [0x143B,"smj-SE","CP1252"],
218 [0x0C3B,"se-FI","CP1252"],
219 [0x043B,"se-NO","CP1252"],
220 [0x083B,"se-SE","CP1252"],
221 [0x203B,"sms-FI","CP1252"],
222 [0x183B,"sma-NO","CP1252"],
223 [0x1C3B,"sma-SE","CP1252"],
224 [0x004F,"sa","UNICODE"],
225 [0x044F,"sa-IN","UNICODE"],
226 [0x7C1A,"sr","CP1251"],
227 [0x1C1A,"sr-Cyrl-BA","CP1251"],
228 [0x0C1A,"sr-Cyrl-SP","CP1251"],
229 [0x181A,"sr-Latn-BA","CP1250"],
230 [0x081A,"sr-Latn-SP","CP1250"],
231 [0x046C,"nso-ZA","CP1252"],
232 [0x0432,"tn-ZA","CP1252"],
233 [0x045B,"si-LK","UNICODE"],
234 [0x001B,"sk","CP1250"],
235 [0x041B,"sk-SK","CP1250"],
236 [0x0024,"sl","CP1250"],
237 [0x0424,"sl-SI","CP1250"],
238 [0x000A,"es","CP1252"],
239 [0x2C0A,"es-AR","CP1252"],
240 [0x400A,"es-BO","CP1252"],
241 [0x340A,"es-CL","CP1252"],
242 [0x240A,"es-CO","CP1252"],
243 [0x140A,"es-CR","CP1252"],
244 [0x1C0A,"es-DO","CP1252"],
245 [0x300A,"es-EC","CP1252"],
246 [0x440A,"es-SV","CP1252"],
247 [0x100A,"es-GT","CP1252"],
248 [0x480A,"es-HN","CP1252"],
249 [0x080A,"es-MX","CP1252"],
250 [0x4C0A,"es-NI","CP1252"],
251 [0x180A,"es-PA","CP1252"],
252 [0x3C0A,"es-PY","CP1252"],
253 [0x280A,"es-PE","CP1252"],
254 [0x500A,"es-PR","CP1252"],
255 [0x0C0A,"es-ES","CP1252"],
256 [0x540A,"es-US","CP1252"],
257 [0x380A,"es-UY","CP1252"],
258 [0x200A,"es-VE","CP1252"],
259 [0x001D,"sv","CP1252"],
260 [0x081D,"sv-FI","CP1252"],
261 [0x041D,"sv-SE","CP1252"],
262 [0x005A,"syr","UNICODE"],
263 [0x045A,"syr-SY","UNICODE"],
264 [0x0428,"tg-Cyrl-TJ","CP1251"],
265 [0x085F,"tmz-Latn-DZ","CP1252"],
266 [0x0049,"ta","UNICODE"],
267 [0x0449,"ta-IN","UNICODE"],
268 [0x0044,"tt","CP1251"],
269 [0x0444,"tt-RU","CP1251"],
270 [0x004A,"te","UNICODE"],
271 [0x044A,"te-IN","UNICODE"],
272 [0x001E,"th","CP874"],
273 [0x041E,"th-TH","CP874"],
274 [0x0451,"bo-CN","UNICODE"],
275 [0x001F,"tr","CP1254"],
276 [0x041F,"tr-TR","CP1254"],
277 [0x0442,"tk-TM","CP1250"],
278 [0x0480,"ug-CN","CP1256"],
279 [0x0022,"uk","CP1251"],
280 [0x0422,"uk-UA","CP1251"],
281 [0x042E,"wen-DE","CP1252"],
282 [0x0020,"ur","CP1256"],
283 [0x0420,"ur-PK","CP1256"],
284 [0x0043,"uz","CP1254"],
285 [0x0843,"uz-Cyrl-UZ","CP1251"],
286 [0x0443,"uz-Latn-UZ","CP1254"],
287 [0x002A,"vi","CP1258"],
288 [0x042A,"vi-VN","CP1258"],
289 [0x0452,"cy-GB","CP1252"],
290 [0x0488,"wo-SN","CP1252"],
291 [0x0485,"sah-RU","CP1251"],
292 [0x0478,"ii-CN","UNICODE"],
293 [0x046A,"yo-NG","CP1252"],
294 ]
295 end
296 end
297 end
+0
-24
vendor/locale/driver.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 #
4 # License: Ruby's or LGPL
5 #
6 # This library is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 module Locale
20 module Driver
21 MODULES = {}
22 end
23 end
+0
-139
vendor/locale/info/language.rb less more
0 # encoding: UTF-8
1 =begin
2
3 language.rb - Locale::Info::Language class
4
5 Copyright (C) 2008 Masao Mutoh
6
7 Original Author:: Brian Pontarelli
8
9 $Id: language.rb 27 2008-12-03 15:06:50Z mutoh $
10 =end
11
12 require 'zlib'
13
14 module Locale
15
16 module Info
17 # This class contains all the of the ISO information for the ISO 639-3
18 # languages. This class is immutable once constructed.
19 class Language
20 attr_reader :two_code, :three_code, :scope, :type, :name
21
22 #
23 # Constructs a new Language instance.
24 #
25 # * code The 2 or 3 digit ISO 639-3 language code.
26 # * scope A single character that defines the ISO scope of the language - <tt>(I)ndividual</tt>,
27 # <tt>(M)acrolanguage</tt>, or <tt>(S)pecial</tt>.
28 # * type A single character that defines the ISO type of the language - <tt>(A)ncient</tt>,
29 # <tt>(C)onstructed</tt>, <tt>(E)xtinct</tt>, <tt>(H)istorical</tt>, <tt>(L)iving</tt>,
30 # or <tt>(S)pecial</tt>.
31 # * name The name of the language.
32 #
33 def initialize(two_code, three_code, scope, type, name)
34 @two_code, @three_code, @scope, @type, @name = two_code, three_code, scope, type, name
35
36 @individual = (scope == "I")
37 @macro = (scope == "M")
38 @special = (scope == "S")
39 @constructed = (type == "C")
40 @living = (type == "L")
41 @extinct = (type == "E")
42 @ancient = (type == "A")
43 @historical = (type == "H")
44 @special_type = (type == "S")
45 end
46
47 # Returns true if the language is an individual language according to the ISO 639-3 data.
48 def individual?; @individual; end
49
50 # Returns true if the language is a macro language according to the ISO 639-3 data.
51 def macro?; @macro; end
52
53 # Returns true if the language is a special language according to the ISO 639-3 data.
54 def special?; @special; end
55
56 # Returns true if the language is a constructed language according to the ISO 639-3 data.
57 def constructed?; @constructed; end
58
59 # Returns true if the language is a living language according to the ISO 639-3 data.
60 def living?; @living; end
61
62 # Returns true if the language is an extinct language according to the ISO 639-3 data.
63 def extinct?; @extinct; end
64
65 # Returns true if the language is an ancient language according to the ISO 639-3 data.
66 def ancient?; @ancient; end
67
68 # Returns true if the language is an historical language according to the ISO 639-3 data.
69 def historical?; @historical; end
70
71 # Returns true if the language is a special type language according to the ISO 639-3 data.
72 def special_type?; @special_type; end
73
74 # Returns the two or three code.
75 def to_s
76 if two_code and two_code.size > 0
77 two_code
78 else
79 three_code
80 end
81 end
82
83 # Returns this object is valid as ISO 639 data.
84 def iso_language?
85 @@lang_two_codes[two_code] != nil || @@lang_three_codes[three_code] != nil
86 end
87 end
88
89 @@lang_two_codes = Hash.new
90 @@lang_three_codes = Hash.new
91
92 Zlib::GzipReader.open(File.dirname(__FILE__) + "/../data/languages.tab.gz") do |gz|
93 gz.readlines.each do |l|
94 l.force_encoding('UTF-8') if l.respond_to?(:force_encoding)
95 unless l =~ /^\s*$/
96 parts = l.split(/\t/)
97 lang = Language.new(parts[2], parts[0], parts[3], parts[4], parts[5].strip)
98 @@lang_three_codes[parts[0]] = lang
99 @@lang_two_codes[parts[2]] = lang if parts[2].length > 0
100 end
101 end
102 end
103
104 module_function
105
106 # Returns a hash of all the ISO languages. The hash is {String, language} where
107 # the string is the 3 digit language code from the ISO 639 data. This contains
108 # all of the data from the ISO 639-3 data (7600 Languages).
109 #
110 # Need to require 'locale/info' or 'locale/language'.
111 def three_languages
112 @@lang_three_codes
113 end
114
115 # Returns a hash of all the ISO languages. The hash is {String, language} where
116 # the string is the 2 digit language code from the ISO 639-1 data. This contains
117 # all of the data from the ISO 639-1 data (186 Languages).
118 #
119 # Need to require 'locale/info' or 'locale/language'.
120 def two_languages
121 @@lang_two_codes
122 end
123
124 # Returns the language for the given 2 or 3 digit code.
125 #
126 # Need to require 'locale/info' or 'locale/language'.
127 def get_language(code)
128 @@lang_three_codes[code] || @@lang_two_codes[code]
129 end
130
131 # Returns the language code is valid.
132 #
133 # Need to require 'locale/info' or 'locale/language'.
134 def language_code?(code)
135 get_language(code) != nil
136 end
137 end
138 end
+0
-75
vendor/locale/info/region.rb less more
0 # encoding: UTF-8
1 =begin
2
3 region.rb - Locale::Info::Region class
4
5 Copyright (C) 2008 Masao Mutoh
6
7 First Author:: Brian Pontarelli
8
9 $Id: region.rb 27 2008-12-03 15:06:50Z mutoh $
10 =end
11
12 require 'zlib'
13
14 module Locale
15
16 module Info
17 # This class models out a region/country from the ISO 3166 standard for region codes.
18 # In ISO3166, it's called "Country" but Ruby/Locale the word "Region" instead.
19 class Region
20 attr_reader :code, :name
21
22 # code:: The 2 or 3 digit ISO 3166 region code.
23 # name:: The name of the region.
24 def initialize(code, name)
25 @code = code
26 @name = name
27 end
28
29 def iso_region?
30 @@regions[code] != nil
31 end
32
33 def to_s
34 "#{code}"
35 end
36 end
37
38 @@regions = Hash.new
39 Zlib::GzipReader.open(File.dirname(__FILE__) + "/../data/regions.tab.gz") do |gz|
40 gz.readlines.each do |l|
41 l.force_encoding('UTF-8') if l.respond_to?(:force_encoding)
42 unless l =~ /^\s*$/
43 parts = l.split(/\t/)
44 region = Region.new(parts[0], parts[1].strip)
45 @@regions[parts[0]] = region
46 end
47 end
48 end
49
50 module_function
51
52 # Returns a hash of all the ISO regions. The hash is {String, Region} where
53 # the string is the 2 digit region code from the ISO 3166 data.
54 #
55 # You need to require 'locale/info' or 'locale/region'.
56 def regions
57 @@regions
58 end
59
60 # Returns the region for the given code.
61 #
62 # You need to require 'locale/info' or 'locale/info/region'.
63 def get_region(code)
64 @@regions[code]
65 end
66
67 # Returns the region code is valid.
68 #
69 # You need to require 'locale/info' or 'locale/info/region'.
70 def valid_region_code?(code)
71 @@regions[code] != nil
72 end
73 end
74 end
+0
-12
vendor/locale/info.rb less more
0 =begin
1
2 info.rb - Load Locale::Info::Language and Locale::Info::Region.
3
4 Copyright (C) 2008 Masao Mutoh
5
6 $Id: info.rb 27 2008-12-03 15:06:50Z mutoh $
7 =end
8
9 require 'locale/info/language'
10 require 'locale/info/region'
11
+0
-38
vendor/locale/middleware.rb less more
0 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
1 #
2 # License: Ruby's or LGPL
3 #
4 # This library is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Lesser General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 require "locale"
18
19 module Locale
20 class Middleware
21 def initialize(application, options={})
22 @application = application
23 @options = options
24 Locale.init(:driver => :cgi)
25 end
26
27 def call(environment)
28 request = Rack::Request.new(environment)
29 Locale.set_request([request["lang"]],
30 [request.cookies["lang"]],
31 environment["HTTP_ACCEPT_LANGUAGE"],
32 environment["HTTP_ACCEPT_CHARSET"])
33 @application.call(environment)
34 end
35 end
36 end
37
+0
-96
vendor/locale/tag/cldr.rb less more
0 =begin
1 locale/tag/cldr.rb - Locale::Tag::CLDR
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/common'
10
11 module Locale
12 module Tag
13
14 # Unicode locale identifier class for CLDR-1.6.1.
15 # (Unicode Common Locale Data Repository).
16 class Cldr < Common
17
18 VARIANT = "(#{ALPHANUM}{5,8}|#{DIGIT}#{ALPHANUM}{3})"
19 EXTENSION = "#{ALPHANUM}+=[a-z0-9\-]+"
20
21 TAG_RE = /\A#{LANGUAGE}(?:[-_]#{SCRIPT})?
22 (?:[-_]#{REGION})?((?:[-_]#{VARIANT})*
23 (?:@(#{EXTENSION};?)+)*)\Z/ix
24
25 attr_reader :extensions
26
27 class << self
28 # Parse the language tag and return the new Locale::Tag::CLDR.
29 def parse(tag)
30 if tag =~ /\APOSIX\Z/ # This is the special case of POSIX locale but match this regexp.
31 nil
32 elsif tag =~ TAG_RE
33 lang, script, region, subtag = $1, $2, $3, $4
34
35 extensions = {}
36 subtag.scan(/#{EXTENSION}/i).each{|v|
37 subtag.sub!(v, "")
38 key, type = v.split("=")
39 extensions[key] = type
40 }
41 variants = subtag.scan(/#{VARIANT}/i).collect{|v| v[0].upcase}
42
43 ret = self.new(lang, script, region, variants, extensions)
44 ret.tag = tag
45 ret
46 else
47 nil
48 end
49 end
50 end
51
52 # Create Locale::Tag::Cldr.
53 #
54 # variants should be upcase.
55 def initialize(language, script = nil, region = nil,
56 variants = [], extensions = {})
57 @extensions = extensions
58 super(language, script, region, variants.map{|v| v.upcase})
59 end
60
61 # Sets the extensions as an Hash.
62 def extensions=(val)
63 @extensions = val
64 end
65
66 private
67 def convert_to(klass) # :nodoc:
68 if klass == Cldr
69 klass.new(language, script, region, variants, extensions)
70 elsif klass == Rfc
71 exts = []
72 @extensions.to_a.sort.each do |k, v|
73 exts << "k-#{k[0,8]}-#{v[0,8]}"
74 end
75
76 klass.new(language, script, region, variants, exts)
77 else
78 super
79 end
80 end
81
82 # Returns the language tag.
83 # (e.g.) "ja_Hira_JP_VARIANT1_VARIANT2@foo1=var1;foo2=var2"
84 #
85 # This is used in internal only. Use to_s instead.
86 def to_string
87 s = super
88 if @extensions.size > 0
89 s << "@" << @extensions.to_a.sort.map{|k, v| "#{k}=#{v}"}.join(";")
90 end
91 s
92 end
93 end
94 end
95 end
+0
-124
vendor/locale/tag/common.rb less more
0 =begin
1 locale/tag/common.rb - Locale::Tag::Common
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/simple'
10
11 module Locale
12 module Tag
13 # Common Language tag class for Ruby.
14 # Java and MS Windows use this format.
15 #
16 # * ja (language: RFC4646)
17 # * ja_JP (country: RFC4646(2 alpha or 3 digit))
18 # * ja-JP
19 # * ja_Hira_JP (script: 4 characters)
20 # * ja-Hira-JP
21 # * ja_Hira_JP_MOBILE (variants: more than 2 characters or 3 digit)
22 # * ja_Hira_JP_MOBILE_IPHONE (2 variants example)
23 #
24 class Common < Simple
25 LANGUAGE = "(#{ALPHA}{2,3}|#{ALPHA}{4}|#{ALPHA}{5,8})" #RFC4646 (ISO639/reserved/registered)
26 SCRIPT = "(#{ALPHA}{4})"
27 VARIANT = "(#{ALPHANUM}{3,}|#{DIGIT}#{ALPHANUM}{3})" #RFC3066 compatible
28
29 TAG_RE = /\A#{LANGUAGE}(?:[-_]#{SCRIPT})?
30 (?:[-_]#{REGION})?((?:[-_]#{VARIANT})*)\Z/ix
31
32 attr_reader :script, :variants
33
34 class << self
35 # Parse the language tag and return the new Locale::Tag::Common.
36 def parse(tag)
37 if tag =~ /\APOSIX\Z/ # This is the special case of POSIX locale but match this regexp.
38 nil
39 elsif tag =~ TAG_RE
40 lang, script, region, subtag = $1, $2, $3, $4
41 variants = subtag.scan(/(^|[-_])#{VARIANT}(?=([-_]|$))/i).collect{|v| v[1]}
42
43 ret = self.new(lang, script, region, variants)
44 ret.tag = tag
45 ret
46 else
47 nil
48 end
49 end
50 end
51
52 # Create a Locale::Tag::Common.
53 def initialize(language, script = nil, region = nil, variants = [])
54 @script, @variants = script, variants
55 @script = @script.capitalize if @script
56 super(language, region)
57 end
58
59 # Set the script (with capitalize)
60 def script=(val)
61 @script = val
62 @script = @script.capitalize if @script
63 @script
64 end
65
66 # Set the variants as an Array.
67 def variants=(val)
68 @variants = val
69 end
70
71 # Returns an Array of tag-candidates order by priority.
72 # Use Locale.candidates instead of this method.
73 #
74 # Locale::Tag::Rfc, Cldr don't have their own candidates,
75 # because it's meaningless to compare the extensions, privateuse, etc.
76 def candidates
77 [self.class.new(language, script, region, variants), #ja-Kana-JP-FOO
78 self.class.new(language, script, region), #ja-Kana-JP
79 self.class.new(language, nil, region, variants), #ja-JP-FOO
80 self.class.new(language, nil, region), #ja-JP
81 self.class.new(language, script, nil, variants), #ja-Kana-FOO
82 self.class.new(language, script), #ja-Kana
83 self.class.new(language, nil, nil, variants), #ja-FOO
84 self.class.new(language)] #ja
85 end
86
87 private
88 def convert_to(klass) #:nodoc:
89 if klass == Simple
90 super
91 elsif klass == Posix
92 if variants.size > 0
93 var = variants.join("-")
94 else
95 var = nil
96 end
97 klass.new(language, region, nil, var)
98 elsif klass == Cldr
99 klass.new(language, script, region, variants.map{|v| v.upcase})
100 else
101 klass.new(language, script, region, variants)
102 end
103 end
104
105 # Returns the common language tag with "_".
106 # <language>_<Script>_<REGION>_VARIANTS1_VARIANTS2
107 # (e.g.) "ja_Hira_JP_VARIANTS1_VARIANTS2"
108 #
109 # This is used in internal only. Use to_s instead.
110 def to_string
111 s = @language.dup
112
113 s << "_" << @script if @script
114 s << "_" << @region if @region
115
116 @variants.each do |v|
117 s << "_#{v}"
118 end
119 s
120 end
121 end
122 end
123 end
+0
-38
vendor/locale/tag/irregular.rb less more
0 =begin
1 locale/tag/irregular.rb - Locale::Tag::Irregular
2
3 Copyright (C) 2008 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 $Id: irregular.rb 27 2008-12-03 15:06:50Z mutoh $
9 =end
10
11 require 'locale/tag/simple'
12
13 module Locale
14
15 module Tag
16 # Broken tag class.
17 class Irregular < Simple
18
19 def initialize(tag)
20 tag = "en" if tag == nil or tag == ""
21 super(tag.to_s)
22 @tag = tag
23 end
24
25 # Returns an Array of tag-candidates order by priority.
26 def candidates
27 [Irregular.new(tag)]
28 end
29
30 # Conver to the klass(the class of Language::Tag)
31 private
32 def convert_to(klass)
33 klass.new(tag)
34 end
35 end
36 end
37 end
+0
-95
vendor/locale/tag/posix.rb less more
0 =begin
1 locale/tag/posix.rb - Locale::Tag::Posix
2
3 Copyright (C) 2008 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 $Id: posix.rb 27 2008-12-03 15:06:50Z mutoh $
9 =end
10
11 module Locale
12 module Tag
13
14 # Locale tag class for POSIX locale
15 # * ja
16 # * ja_JP
17 # * ja_JP.UTF-8
18 # * ja_JP.UTF-8@Osaka
19 # * C/POSIX (-> en_US)
20 class Posix < Simple
21 LANGUAGE = "([a-z]{2,})"
22 TAG_RE = /\A#{LANGUAGE}(?:_#{REGION})?(?:\.([^@]+))?(?:@(.*))?\Z/i
23
24 attr_reader :charset, :modifier
25
26 def initialize(language, region = nil, charset = nil, modifier = nil)
27 @charset, @modifier = charset, modifier
28 super(language, region)
29 end
30
31 def self.parse(tag)
32 if tag =~ /^(C|POSIX)$/
33 ret = self.new("en", "US")
34 ret.tag = tag
35 ret
36 elsif tag =~ TAG_RE
37 ret = self.new($1, $2, $3, $4)
38 ret.tag = tag
39 ret
40 else
41 nil
42 end
43 end
44
45 # Returns the language tag.
46 # <language>_<COUNTRY>.<CHARSET>@<MODIFIER>
47 # (e.g.) "ja_JP.EUC-JP@Modifier"
48 def to_s
49 s = @language.dup
50 s << "_#{@region}" if @region
51 s << ".#{@charset}" if @charset
52 s << "@#{@modifier}" if @modifier
53 s
54 end
55
56 # Set the charset.
57 def charset=(val)
58 @charset = val
59 end
60
61 # Set the modifier as a String
62 def modifier=(val)
63 @modifier = val
64 end
65
66 # Returns an Array of tag-candidates order by priority.
67 # Use Locale.candidates instead of this method.
68 def candidates
69 [self.class.new(language, region, charset, modifier), #ja_JP.UTF-8@Modifier
70 self.class.new(language, region, charset), #ja_JP.UTF-8
71 self.class.new(language, region, nil, modifier), #ja_JP@Modifier
72 self.class.new(language, region, nil, nil), #ja_JP@Modifier
73 self.class.new(language, nil, charset, modifier), #ja.UTF-8@Modifier
74 self.class.new(language, nil, charset), #ja.UTF-8
75 self.class.new(language, nil, nil, modifier), #ja@Modifier
76 self.class.new(language)] #ja
77 end
78
79 # A modifier is converted to a variant.
80 # If the modifier is less than 5 characters, it is not canonical value.
81 private
82 def convert_to(klass)
83 if klass == Simple
84 super
85 elsif klass == Posix
86 klass.new(language, region, charset, modifier)
87 else
88 klass.new(language, nil, region, modifier ? [modifier] : [])
89 end
90 end
91
92 end
93 end
94 end
+0
-108
vendor/locale/tag/rfc.rb less more
0 =begin
1 locale/tag/rfc.rb - Locale::Tag::Rfc
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/common'
10
11 module Locale
12 module Tag
13
14 # Language tag class for RFC4646(BCP47).
15 class Rfc < Common
16 SINGLETON = '[a-wyz0-9]'
17 VARIANT = "(#{ALPHANUM}{5,8}|#{DIGIT}#{ALPHANUM}{3})"
18 EXTENSION = "(#{SINGLETON}(?:-#{ALPHANUM}{2,8})+)"
19 PRIVATEUSE = "(x(?:-#{ALPHANUM}{1,8})+)"
20 GRANDFATHERED = "#{ALPHA}{1,3}(?:-#{ALPHANUM}{2,8}){1,2}"
21
22 TAG_RE = /\A#{LANGUAGE}(?:-#{SCRIPT})?
23 (?:-#{REGION})?((?:-#{VARIANT})*
24 (?:-#{EXTENSION})*(?:-#{PRIVATEUSE})?)\Z/ix
25
26 attr_reader :extensions, :privateuse
27
28 class << self
29 # Parse the language tag and return the new Locale::Tag::Rfc.
30 def parse(tag)
31 if tag =~ /\APOSIX\Z/ # This is the special case of POSIX locale but match this regexp.
32 nil
33 elsif tag =~ TAG_RE
34 lang, script, region, subtag = $1, $2, $3, $4
35 extensions = []
36 variants = []
37 if subtag =~ /#{PRIVATEUSE}/
38 subtag, privateuse = $`, $1
39 # Private use for CLDR.
40 if /x-ldml(.*)/ =~ privateuse
41 p_subtag = $1
42 extensions = p_subtag.scan(/(^|-)#{EXTENSION}/i).collect{|v| p_subtag.sub!(v[1], ""); v[1]}
43 variants = p_subtag.scan(/(^|-)#{VARIANT}(?=(-|$))/i).collect{|v| v[1]}
44 end
45 end
46 extensions += subtag.scan(/(^|-)#{EXTENSION}/i).collect{|v| subtag.sub!(v[1], ""); v[1]}
47 variants += subtag.scan(/(^|-)#{VARIANT}(?=(-|$))/i).collect{|v| v[1]}
48
49 ret = self.new(lang, script, region, variants, extensions, privateuse)
50 ret.tag = tag
51 ret
52 else
53 nil
54 end
55 end
56 end
57
58 def initialize(language, script = nil, region = nil, variants = [],
59 extensions = [], privateuse = nil)
60 @extensions, @privateuse = extensions, privateuse
61 super(language, script, region, variants)
62 end
63
64 # Sets the extensions as an Array.
65 def extensions=(val)
66 @extensions = val
67 end
68
69 # Sets the privateuse as a String
70 def privateuse=(val)
71 @privateuse = val
72 end
73
74 private
75 def convert_to(klass)
76 if klass == Rfc
77 klass.new(language, script, region, variants, extensions, privateuse)
78 elsif klass == Cldr
79 exts = {}
80 extensions.sort.each do |v|
81 if v =~ /^k-(#{ALPHANUM}{2,})-(.*)$/i
82 exts[$1] = $2
83 end
84 end
85 klass.new(language, script, region, variants, exts)
86 else
87 super
88 end
89 end
90
91 # Returns the language tag
92 # <language>-<Script>-<REGION>-<variants>-<extensions>-<PRIVATEUSE>
93 # (e.g.) "ja-Hira-JP-variant"
94 #
95 # This is used in internal only. Use to_s instead.
96 def to_string
97 s = super.gsub(/_/, "-")
98 @extensions.sort.each do |v|
99 s << "-#{v}"
100 end
101 s << "-#{@privateuse}" if @privateuse
102 s
103 end
104
105 end
106 end
107 end
+0
-146
vendor/locale/tag/simple.rb less more
0 =begin
1 locale/tag/simple.rb - Locale::Tag::Simple
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 module Locale
10 module Tag
11 # Abstract language tag class.
12 # This class has <language>, <region> which
13 # all of language tag specifications have.
14 #
15 # * ja (language: ISO 639 (2 or 3 alpha))
16 # * ja_JP (country: RFC4646 (ISO3166/UN M.49) (2 alpha or 3 digit)
17 # * ja-JP
18 # * ja-392
19 class Simple
20 ALPHA = '[a-z]'
21 DIGIT = '[0-9]'
22 ALPHANUM = "[a-zA-Z0-9]"
23
24 LANGUAGE = "(#{ALPHA}{2,3})" # ISO 639
25 REGION = "(#{ALPHA}{2}|#{DIGIT}{3})" # RFC4646 (ISO3166/UN M.49)
26
27 TAG_RE = /\A#{LANGUAGE}(?:[_-]#{REGION})?\Z/i
28
29 attr_reader :language, :region
30
31 # tag is set when .parse method is called.
32 # This value is used when the program want to know the original
33 # String.
34 attr_accessor :tag
35
36 # call-seq:
37 # to_common
38 # to_posix
39 # to_rfc
40 # to_cldr
41 #
42 # Convert to each tag classes.
43 [:simple, :common, :posix, :rfc, :cldr].each do |name|
44 class_eval <<-EOS
45 def to_#{name}
46 convert_to(#{name.to_s.capitalize})
47 end
48 EOS
49 end
50
51 class << self
52 # Parse the language tag and return the new Locale::Tag::Simple.
53 def parse(tag)
54 if tag =~ TAG_RE
55 ret = self.new($1, $2)
56 ret.tag = tag
57 ret
58 else
59 nil
60 end
61 end
62 end
63
64 # Create a Locale::Tag::Simple
65 def initialize(language, region = nil)
66 raise "language can't be nil." unless language
67 @language, @region = language, region
68 @language = @language.downcase if @language
69 @region = @region.upcase if @region
70 end
71
72 # Returns the language tag as the String.
73 # <language>_<REGION>
74 # (e.g.) "ja_JP"
75 def to_s
76 to_string
77 end
78
79 def to_str #:nodoc:
80 to_s
81 end
82
83 def <=>(other)
84 self.to_s <=> other.to_s
85 end
86
87 def ==(other) #:nodoc:
88 other != nil and hash == other.hash
89 end
90
91 def eql?(other) #:nodoc:
92 self.==(other)
93 end
94
95 def hash #:nodoc:
96 "#{self.class}:#{to_s}".hash
97 end
98
99 def inspect #:nodoc:
100 %Q[#<#{self.class}: #{to_s}>]
101 end
102
103 # For backward compatibility.
104 def country; region end
105
106 # Set the language (with downcase)
107 def language=(val)
108 @language = val
109 @language = @language.downcase if @language
110 @language
111 end
112
113 # Set the region (with upcase)
114 def region=(val)
115 @region = val
116 @region = @region.upcase if @region
117 @region
118 end
119
120 # Returns an Array of tag-candidates order by priority.
121 # Use Locale.candidates instead of this method.
122 def candidates
123 [self.class.new(language, region), self.class.new(language)]
124 end
125
126 # Convert to the klass(the class of Language::Tag)
127 private
128 def convert_to(klass) #:nodoc:
129 if klass == Simple || klass == Posix
130 klass.new(language, region)
131 else
132 klass.new(language, nil, region)
133 end
134 end
135
136 # Return simple language tag which format is"<lanuguage>_<REGION>".
137 # This is to use internal only. Use to_s instead.
138 def to_string
139 s = @language.dup
140 s << "_" << @region if @region
141 s
142 end
143 end
144 end
145 end
+0
-36
vendor/locale/tag.rb less more
0 =begin
1 tag.rb - Locale::Tag module
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/simple'
10 require 'locale/tag/irregular'
11 require 'locale/tag/common'
12 require 'locale/tag/rfc'
13 require 'locale/tag/cldr'
14 require 'locale/tag/posix'
15
16 module Locale
17
18 # Language tag / locale identifiers.
19 module Tag
20 module_function
21 # Parse a language tag/locale name and return Locale::Tag
22 # object.
23 # * tag: a tag as a String. e.g.) ja-Hira-JP
24 # * Returns: a Locale::Tag subclass.
25 def parse(tag)
26 # Common is not used here.
27 [Simple, Common, Rfc, Cldr, Posix].each do |parser|
28 ret = parser.parse(tag)
29 return ret if ret
30 end
31 Locale::Tag::Irregular.new(tag)
32 end
33 end
34 end
35
+0
-102
vendor/locale/taglist.rb less more
0 =begin
1 taglist.rb - Locale module
2
3 Copyright (C) 2008 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 $Id: taglist.rb 27 2008-12-03 15:06:50Z mutoh $
9 =end
10
11 module Locale
12 # This provides the subclass of Array which behaves like
13 # the first(top priority) Locale::Tag object.
14 # "Locale.current.language" is same with "Locale.current[0].language".
15 #
16 # Locale.current returns an Array of Tag(s) now.
17 # But the old Locale.current(Ruby-GetText) and Locale.get
18 # returns Locale::Object (similier with Locale::Tag::Posix).
19 # This is the class for backward compatibility.
20 #
21 # It is recommanded to use Locale.current[0] or
22 # Locale.candidates to find the current locale instead
23 # of this function.
24 #
25 class TagList < Array
26 # Returns the top priority language. (simple)
27 def language
28 self[0].language
29 end
30 # Returns the top priority region/country. (simple)
31 def country
32 self[0].region
33 end
34 # Returns the top priority region/country. (simple)
35 def region
36 self[0].region
37 end
38 # Returns the top priority script. (common)
39 def script
40 self[0].script
41 end
42 # Returns the top priority charset. (posix)
43 def charset
44 top_priority_charset = nil
45 first_tag = self[0]
46 if first_tag.respond_to?(:charset)
47 top_priority_charset = first_tag.charset
48 end
49 top_priority_charset ||= ::Locale.driver_module.charset
50 top_priority_charset
51 end
52
53 # Returns the top priority modifier. (posix)
54 def modifier
55 (self[0].respond_to? :modifier) ? self[0].modifier : nil
56 end
57
58 # Returns the top priority variants.(common, rfc, cldr)
59 def variants
60 (self[0].respond_to? :variants) ? self[0].variants : nil
61 end
62
63 # Returns the top priority extensions.(common, rfc, cldr)
64 def extensions
65 (self[0].respond_to? :extensions) ? self[0].extensions : nil
66 end
67
68 # Returns the top priority privateuse(rfc)
69 def privateuse
70 (self[0].respond_to? :privateuse) ? self[0].privateuse : nil
71 end
72
73 def to_str
74 self[0].to_str
75 end
76
77 def to_s
78 self[0].to_s
79 end
80
81 def to_common
82 self[0].to_common
83 end
84
85 def to_simple
86 self[0].to_simple
87 end
88
89 def to_rfc
90 self[0].to_rfc
91 end
92
93 def to_cldr
94 self[0].to_cldr
95 end
96
97 def to_posix
98 self[0].to_posix
99 end
100 end
101 end
+0
-13
vendor/locale/version.rb less more
0 =begin
1 version - version information of Ruby-Locale
2
3 Copyright (C) 2008 Masao Mutoh
4 Copyright (C) 2013 Kouhei Sutou <kou@clear-code.com>
5
6 You may redistribute it and/or modify it under the same
7 license terms as Ruby.
8 =end
9 module Locale
10 VERSION = "2.1.1"
11 end
12
+0
-327
vendor/locale.rb less more
0 =begin
1 locale.rb - Locale module
2
3 Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4 Copyright (C) 2002-2009 Masao Mutoh
5
6 You may redistribute it and/or modify it under the same
7 license terms as Ruby.
8
9 Original: Ruby-GetText-Package-1.92.0.
10
11 $Id: locale.rb 27 2008-12-03 15:06:50Z mutoh $
12 =end
13
14 require 'locale/tag'
15 require 'locale/taglist'
16 require 'locale/driver'
17 require 'locale/version'
18
19 # Locale module manages the locale informations of the application.
20 # These functions are the most important APIs in this library.
21 # Almost of all i18n/l10n programs use this APIs only.
22 module Locale
23 @@default_tag = nil
24 @@driver_name = nil
25
26 module_function
27 def require_driver(name) #:nodoc:
28 require "locale/driver/#{name}"
29 @@driver_name = name.to_sym
30 end
31
32 def create_language_tag(tag) #:nodoc:
33 case tag
34 when nil
35 when Locale::Tag::Simple
36 tag
37 when Locale::TagList
38 tag[0]
39 else
40 Locale::Tag.parse(tag)
41 end
42 end
43
44 # Initialize Locale library.
45 # Usually, you don't need to call this directly, because
46 # this is called when Locale's methods are called.
47 #
48 # If you use this library with CGI or the kind of CGI.
49 # You need to call Locale.init(:driver => :cgi).
50 #
51 # ==== For Framework designers/programers:
52 # If your framework is for WWW, call this once like: Locale.init(:driver => :cgi).
53 #
54 # ==== To Application programers:
55 # If your framework doesn't use ruby-locale and the application is for WWW,
56 # call this once like: Locale.init(:driver => :cgi).
57 #
58 # ==== To Library authors:
59 # Don't call this, even if your application is only for WWW.
60 #
61 # * opts: Options as a Hash.
62 # * :driver - The driver. :cgi if you use Locale module with CGI,
63 # nil if you use system locale.
64 # (ex) Locale.init(:driver => :cgi)
65 #
66 def init(opts = {})
67 if opts[:driver]
68 require_driver opts[:driver]
69 else
70 if /cygwin|mingw|win32/ =~ RUBY_PLATFORM
71 require_driver 'win32'
72 elsif /java/ =~ RUBY_PLATFORM
73 require_driver 'jruby'
74 else
75 require_driver 'posix'
76 end
77 end
78 end
79
80 # Gets the driver module.
81 #
82 # Usually you don't need to call this method.
83 #
84 # * Returns: the driver module.
85 def driver_module
86 Locale.init if @@driver_name.nil?
87 Driver::MODULES[@@driver_name]
88 end
89
90 DEFAULT_LANGUAGE_TAG = Locale::Tag::Simple.new("en") #:nodoc:
91
92 # Sets the default locale as the language tag
93 # (Locale::Tag's class or String(such as "ja_JP")).
94 #
95 # * tag: the default language_tag
96 # * Returns: self.
97 def set_default(tag)
98 Thread.list.each do |thread|
99 thread[:current_languages] = nil
100 thread[:candidates_caches] = nil
101 end
102 @@default_tag = create_language_tag(tag)
103 self
104 end
105
106 # Same as Locale.set_default.
107 #
108 # * locale: the default locale (Locale::Tag's class) or a String such as "ja-JP".
109 # * Returns: locale.
110 def default=(tag)
111 set_default(tag)
112 @@default_tag
113 end
114
115 # Gets the default locale(language tag).
116 #
117 # If the default language tag is not set, this returns nil.
118 #
119 # * Returns: the default locale (Locale::Tag's class).
120 def default
121 @@default_tag || DEFAULT_LANGUAGE_TAG
122 end
123
124 # Sets the locales of the current thread order by the priority.
125 # Each thread has a current locales.
126 # The system locale/default locale is used if the thread doesn't have current locales.
127 #
128 # * tag: Locale::Language::Tag's class or the language tag as a String. nil if you need to
129 # clear current locales.
130 # * charset: the charset (override the charset even if the locale name has charset) or nil.
131 # * Returns: self
132 #
133 # (e.g.)
134 # Locale.set_current("ja_JP.eucJP")
135 # Locale.set_current("ja-JP")
136 # Locale.set_current("en_AU", "en_US", ...)
137 # Locale.set_current(Locale::Tag::Simple.new("ja", "JP"), ...)
138 def set_current(*tags)
139 languages = nil
140 if tags[0]
141 languages = Locale::TagList.new
142 tags.each do |tag|
143 case tag
144 when Locale::TagList
145 languages.concat(tag)
146 else
147 languages << create_language_tag(tag)
148 end
149 end
150 end
151 Thread.current[:current_languages] = languages
152 Thread.current[:candidates_caches] = nil
153 self
154 end
155
156 # Sets a current locale. This is a single argument version of Locale.set_current.
157 #
158 # * tag: the language tag such as "ja-JP"
159 # * Returns: an Array of the current locale (Locale::Tag's class).
160 #
161 # Locale.current = "ja-JP"
162 # Locale.current = "ja_JP.eucJP"
163 def current=(tag)
164 set_current(tag)
165 Thread.current[:current_languages]
166 end
167
168 # Gets the current locales (Locale::Tag's class).
169 # If the current locale is not set, this returns system/default locale.
170 #
171 # This method returns the current language tags even if it isn't included in app_language_tags.
172 #
173 # Usually, the programs should use Locale.candidates to find the correct locale, not this method.
174 #
175 # * Returns: an Array of the current locales (Locale::Tag's class).
176 def current
177 unless Thread.current[:current_languages]
178 loc = driver_module.locales
179 Thread.current[:current_languages] = loc ? loc : Locale::TagList.new([default])
180 end
181 Thread.current[:current_languages]
182 end
183
184 # Deprecated.
185 def get #:nodoc:
186 current
187 end
188
189 # Deprecated.
190 def set(tag) #:nodoc:
191 set_current(tag)
192 end
193
194 # Returns the language tags which are variations of the current locales order by priority.
195 #
196 # For example, if the current locales are ["fr", "ja_JP", "en_US", "en-Latn-GB-VARIANT"],
197 # then returns ["fr", "ja_JP", "en_US", "en-Latn-GB-VARIANT", "en_Latn_GB", "en_GB", "ja", "en"].
198 # "en" is the default locale(You can change it using set_default).
199 # The default locale is added at the end of the list even if it isn't exist.
200 #
201 # Usually, this method is used to find the locale data as the path(or a kind of IDs).
202 # * options: options as a Hash or nil.
203 # * :supported_language_tags -
204 # An Array of the language tags order by the priority. This option
205 # restricts the locales which are supported by the library/application.
206 # Default is nil if you don't need to restrict the locales.
207 # (e.g.1) ["fr_FR", "en_GB", "en_US", ...]
208 # * :type -
209 # The type of language tag. :common, :rfc, :cldr, :posix and
210 # :simple are available. Default value is :common
211 def candidates(options = {})
212 opts = {
213 :supported_language_tags => nil,
214 :current => current,
215 :type => :common,
216 }.merge(options)
217
218 Thread.current[:candidates_caches] ||= {}
219 Thread.current[:candidates_caches][opts] ||=
220 collect_candidates(opts[:type], opts[:current],
221 opts[:supported_language_tags])
222 end
223
224 # collect tag candidates.
225 # The result is shared from all threads.
226 def collect_candidates(type, tags, supported_tags) # :nodoc:
227 candidate_tags = tags.collect{|v| v.send("to_#{type}").candidates}
228 default_tags = default.send("to_#{type}").candidates
229 if app_language_tags
230 app_tags = app_language_tags.collect{|v| v.send("to_#{type}")}.flatten.uniq
231 end
232 if supported_tags
233 supported_tags = supported_tags.collect{|v| Locale::Tag.parse(v).send("to_#{type}")}.flatten.uniq
234 end
235
236 tags = []
237 unless candidate_tags.empty?
238 (0...candidate_tags[0].size).each {|i|
239 tags += candidate_tags.collect{|v| v[i]}
240 }
241 end
242 tags += default_tags
243 tags.uniq!
244
245 all_tags = nil
246 if app_tags
247 if supported_tags
248 all_tags = app_tags & supported_tags
249 else
250 all_tags = app_tags
251 end
252 elsif supported_tags
253 all_tags = supported_tags
254 end
255 if all_tags
256 tags &= all_tags
257 tags = default_tags.uniq if tags.size == 0
258 end
259
260 Locale::TagList.new(tags)
261 end
262
263 # Gets the current charset.
264 #
265 # This returns the current user/system charset. This value is
266 # read only, so you can't set it by yourself.
267 #
268 # * Returns: the current charset.
269 def charset
270 driver_module.charset || "UTF-8"
271 end
272
273 # Clear current locale.
274 # * Returns: self
275 def clear
276 Thread.current[:current_languages] = nil
277 Thread.current[:candidates_caches] = nil
278 self
279 end
280
281 # Clear all locales and charsets of all threads.
282 # This doesn't clear the default and app_language_tags.
283 # Use Locale.default = nil to unset the default locale.
284 # * Returns: self
285 def clear_all
286 Thread.list.each do |thread|
287 thread[:current_languages] = nil
288 thread[:candidates_caches] = nil
289 end
290 self
291 end
292
293 @@app_language_tags = nil
294 # Set the language tags which is supported by the Application.
295 # This value is same with supported_language_tags in Locale.candidates
296 # to restrict the result but is the global setting.
297 # If you set a language tag, the application works as the single locale
298 # application.
299 #
300 # If the current locale is not included in app_language_tags,
301 # Locale.default value is used.
302 # Use Locale.set_default() to set correct language
303 # if "en" is not included in the language tags.
304 #
305 # Set nil if clear the value.
306 #
307 # Note that the libraries/plugins shouldn't set this value.
308 #
309 # (e.g.) Locale.set_app_language_tags("fr_FR", "en-GB", "en_US", ...)
310 def set_app_language_tags(*tags)
311 if tags[0]
312 @@app_language_tags = tags.collect{|v| Locale::Tag.parse(v)}
313 else
314 @@app_language_tags = nil
315 end
316
317 clear_all
318 self
319 end
320
321 # Returns the app_language_tags. Default is nil. See set_app_language_tags for more details.
322 def app_language_tags
323 @@app_language_tags
324 end
325
326 end
+0
-19
vendor/memoize.rb less more
0 module Memoize
1 # The version of the memoize library
2 MEMOIZE_VERSION = '1.3.1'
3
4 # Memoize the method +name+. If +file+ is provided, then the method results
5 # are stored on disk as well as in memory.
6 def memoize(name, file=nil)
7 cache = File.open(file, 'rb'){ |io| Marshal.load(io) } rescue {}
8
9 (class<<self; self; end).send(:define_method, name) do |*args|
10 unless cache.has_key?(args)
11 cache[args] = super(*args)
12 File.open(file, 'wb'){ |f| Marshal.dump(cache, f) } if file
13 end
14 cache[args]
15 end
16 cache
17 end
18 end
+0
-378
vendor/oauth/cli.rb less more
0 require 'optparse'
1 require 'oauth'
2
3 module OAuth
4 class CLI
5 SUPPORTED_COMMANDS = {
6 "authorize" => "Obtain an access token and secret for a user",
7 "debug" => "Verbosely generate an OAuth signature",
8 "query" => "Query a protected resource",
9 "sign" => "Generate an OAuth signature",
10 "version" => "Display the current version of the library"
11 }
12
13 attr_reader :command
14 attr_reader :options
15 attr_reader :stdout, :stdin
16
17 def self.execute(stdout, stdin, stderr, arguments = [])
18 self.new.execute(stdout, stdin, stderr, arguments)
19 end
20
21 def initialize
22 @options = {}
23
24 # don't dump a backtrace on a ^C
25 trap(:INT) {
26 exit
27 }
28 end
29
30 def execute(stdout, stdin, stderr, arguments = [])
31 @stdout = stdout
32 @stdin = stdin
33 @stderr = stderr
34 extract_command_and_parse_options(arguments)
35
36 if sufficient_options? && valid_command?
37 if command == "debug"
38 @command = "sign"
39 @options[:verbose] = true
40 end
41
42 case command
43 # TODO move command logic elsewhere
44 when "authorize"
45 begin
46 consumer = OAuth::Consumer.new \
47 options[:oauth_consumer_key],
48 options[:oauth_consumer_secret],
49 :access_token_url => options[:access_token_url],
50 :authorize_url => options[:authorize_url],
51 :request_token_url => options[:request_token_url],
52 :scheme => options[:scheme],
53 :http_method => options[:method].to_s.downcase.to_sym
54
55 # parameters for OAuth 1.0a
56 oauth_verifier = nil
57
58 # get a request token
59 request_token = consumer.get_request_token({ :oauth_callback => options[:oauth_callback] }, { "scope" => options[:scope] })
60
61 if request_token.callback_confirmed?
62 stdout.puts "Server appears to support OAuth 1.0a; enabling support."
63 options[:version] = "1.0a"
64 end
65
66 stdout.puts "Please visit this url to authorize:"
67 stdout.puts request_token.authorize_url
68
69 if options[:version] == "1.0a"
70 stdout.puts "Please enter the verification code provided by the SP (oauth_verifier):"
71 oauth_verifier = stdin.gets.chomp
72 else
73 stdout.puts "Press return to continue..."
74 stdin.gets
75 end
76
77 begin
78 # get an access token
79 access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
80
81 stdout.puts "Response:"
82 access_token.params.each do |k,v|
83 stdout.puts " #{k}: #{v}" unless k.is_a?(Symbol)
84 end
85 rescue OAuth::Unauthorized => e
86 stderr.puts "A problem occurred while attempting to obtain an access token:"
87 stderr.puts e
88 stderr.puts e.request.body
89 end
90 rescue OAuth::Unauthorized => e
91 stderr.puts "A problem occurred while attempting to authorize:"
92 stderr.puts e
93 stderr.puts e.request.body
94 end
95 when "query"
96 consumer = OAuth::Consumer.new \
97 options[:oauth_consumer_key],
98 options[:oauth_consumer_secret],
99 :scheme => options[:scheme]
100
101 access_token = OAuth::AccessToken.new(consumer, options[:oauth_token], options[:oauth_token_secret])
102
103 # append params to the URL
104 uri = URI.parse(options[:uri])
105 params = prepare_parameters.map { |k,v| v.map { |v2| "#{URI.encode(k)}=#{URI.encode(v2)}" } * "&" }
106 uri.query = [uri.query, *params].reject { |x| x.nil? } * "&"
107 p uri.to_s
108
109 response = access_token.request(options[:method].downcase.to_sym, uri.to_s)
110 puts "#{response.code} #{response.message}"
111 puts response.body
112 when "sign"
113 parameters = prepare_parameters
114
115 request = OAuth::RequestProxy.proxy \
116 "method" => options[:method],
117 "uri" => options[:uri],
118 "parameters" => parameters
119
120 if verbose?
121 stdout.puts "OAuth parameters:"
122 request.oauth_parameters.each do |k,v|
123 stdout.puts " " + [k, v] * ": "
124 end
125 stdout.puts
126
127 if request.non_oauth_parameters.any?
128 stdout.puts "Parameters:"
129 request.non_oauth_parameters.each do |k,v|
130 stdout.puts " " + [k, v] * ": "
131 end
132 stdout.puts
133 end
134 end
135
136 request.sign! \
137 :consumer_secret => options[:oauth_consumer_secret],
138 :token_secret => options[:oauth_token_secret]
139
140 if verbose?
141 stdout.puts "Method: #{request.method}"
142 stdout.puts "URI: #{request.uri}"
143 stdout.puts "Normalized params: #{request.normalized_parameters}" unless options[:xmpp]
144 stdout.puts "Signature base string: #{request.signature_base_string}"
145
146 if options[:xmpp]
147 stdout.puts
148 stdout.puts "XMPP Stanza:"
149 stdout.puts <<-EOS
150 <oauth xmlns='urn:xmpp:oauth:0'>
151 <oauth_consumer_key>#{request.oauth_consumer_key}</oauth_consumer_key>
152 <oauth_token>#{request.oauth_token}</oauth_token>
153 <oauth_signature_method>#{request.oauth_signature_method}</oauth_signature_method>
154 <oauth_signature>#{request.oauth_signature}</oauth_signature>
155 <oauth_timestamp>#{request.oauth_timestamp}</oauth_timestamp>
156 <oauth_nonce>#{request.oauth_nonce}</oauth_nonce>
157 <oauth_version>#{request.oauth_version}</oauth_version>
158 </oauth>
159 EOS
160 stdout.puts
161 stdout.puts "Note: You may want to use bare JIDs in your URI."
162 stdout.puts
163 else
164 stdout.puts "OAuth Request URI: #{request.signed_uri}"
165 stdout.puts "Request URI: #{request.signed_uri(false)}"
166 stdout.puts "Authorization header: #{request.oauth_header(:realm => options[:realm])}"
167 end
168 stdout.puts "Signature: #{request.oauth_signature}"
169 stdout.puts "Escaped signature: #{OAuth::Helper.escape(request.oauth_signature)}"
170 else
171 stdout.puts request.oauth_signature
172 end
173 when "version"
174 puts "OAuth for Ruby #{OAuth::VERSION}"
175 end
176 else
177 usage
178 end
179 end
180
181 protected
182
183 def extract_command_and_parse_options(arguments)
184 @command = arguments[-1]
185 parse_options(arguments[0..-1])
186 end
187
188 def option_parser(arguments = "")
189 # TODO add realm parameter
190 # TODO add user-agent parameter
191 option_parser = OptionParser.new do |opts|
192 opts.banner = "Usage: #{$0} [options] <command>"
193
194 # defaults
195 options[:oauth_nonce] = OAuth::Helper.generate_key
196 options[:oauth_signature_method] = "HMAC-SHA1"
197 options[:oauth_timestamp] = OAuth::Helper.generate_timestamp
198 options[:oauth_version] = "1.0"
199 options[:method] = :post
200 options[:params] = []
201 options[:scheme] = :header
202 options[:version] = "1.0"
203
204 ## Common Options
205
206 opts.on("-B", "--body", "Use the request body for OAuth parameters.") do
207 options[:scheme] = :body
208 end
209
210 opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v|
211 options[:oauth_consumer_key] = v
212 end
213
214 opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v|
215 options[:oauth_consumer_secret] = v
216 end
217
218 opts.on("-H", "--header", "Use the 'Authorization' header for OAuth parameters (default).") do
219 options[:scheme] = :header
220 end
221
222 opts.on("-Q", "--query-string", "Use the query string for OAuth parameters.") do
223 options[:scheme] = :query_string
224 end
225
226 opts.on("-O", "--options FILE", "Read options from a file") do |v|
227 arguments.unshift(*open(v).readlines.map { |l| l.chomp.split(" ") }.flatten)
228 end
229
230 ## Options for signing and making requests
231
232 opts.separator("\n options for signing and querying")
233
234 opts.on("--method METHOD", "Specifies the method (e.g. GET) to use when signing.") do |v|
235 options[:method] = v
236 end
237
238 opts.on("--nonce NONCE", "Specifies the none to use.") do |v|
239 options[:oauth_nonce] = v
240 end
241
242 opts.on("--parameters PARAMS", "Specifies the parameters to use when signing.") do |v|
243 options[:params] << v
244 end
245
246 opts.on("--signature-method METHOD", "Specifies the signature method to use; defaults to HMAC-SHA1.") do |v|
247 options[:oauth_signature_method] = v
248 end
249
250 opts.on("--secret SECRET", "Specifies the token secret to use.") do |v|
251 options[:oauth_token_secret] = v
252 end
253
254 opts.on("--timestamp TIMESTAMP", "Specifies the timestamp to use.") do |v|
255 options[:oauth_timestamp] = v
256 end
257
258 opts.on("--token TOKEN", "Specifies the token to use.") do |v|
259 options[:oauth_token] = v
260 end
261
262 opts.on("--realm REALM", "Specifies the realm to use.") do |v|
263 options[:realm] = v
264 end
265
266 opts.on("--uri URI", "Specifies the URI to use when signing.") do |v|
267 options[:uri] = v
268 end
269
270 opts.on(:OPTIONAL, "--version VERSION", "Specifies the OAuth version to use.") do |v|
271 if v
272 options[:oauth_version] = v
273 else
274 @command = "version"
275 end
276 end
277
278 opts.on("--no-version", "Omit oauth_version.") do
279 options[:oauth_version] = nil
280 end
281
282 opts.on("--xmpp", "Generate XMPP stanzas.") do
283 options[:xmpp] = true
284 options[:method] ||= "iq"
285 end
286
287 opts.on("-v", "--verbose", "Be verbose.") do
288 options[:verbose] = true
289 end
290
291 ## Options for authorization
292
293 opts.separator("\n options for authorization")
294
295 opts.on("--access-token-url URL", "Specifies the access token URL.") do |v|
296 options[:access_token_url] = v
297 end
298
299 opts.on("--authorize-url URL", "Specifies the authorization URL.") do |v|
300 options[:authorize_url] = v
301 end
302
303 opts.on("--callback-url URL", "Specifies a callback URL.") do |v|
304 options[:oauth_callback] = v
305 end
306
307 opts.on("--request-token-url URL", "Specifies the request token URL.") do |v|
308 options[:request_token_url] = v
309 end
310
311 opts.on("--scope SCOPE", "Specifies the scope (Google-specific).") do |v|
312 options[:scope] = v
313 end
314 end
315 end
316
317 def parse_options(arguments)
318 option_parser(arguments).parse!(arguments)
319 end
320
321 def prepare_parameters
322 escaped_pairs = options[:params].collect do |pair|
323 if pair =~ /:/
324 Hash[*pair.split(":", 2)].collect do |k,v|
325 [CGI.escape(k.strip), CGI.escape(v.strip)] * "="
326 end
327 else
328 pair
329 end
330 end
331
332 querystring = escaped_pairs * "&"
333 cli_params = CGI.parse(querystring)
334
335 {
336 "oauth_consumer_key" => options[:oauth_consumer_key],
337 "oauth_nonce" => options[:oauth_nonce],
338 "oauth_timestamp" => options[:oauth_timestamp],
339 "oauth_token" => options[:oauth_token],
340 "oauth_signature_method" => options[:oauth_signature_method],
341 "oauth_version" => options[:oauth_version]
342 }.reject { |k,v| v.nil? || v == "" }.merge(cli_params)
343 end
344
345 def sufficient_options?
346 case command
347 # TODO move command logic elsewhere
348 when "authorize"
349 options[:oauth_consumer_key] && options[:oauth_consumer_secret] &&
350 options[:access_token_url] && options[:authorize_url] &&
351 options[:request_token_url]
352 when "version"
353 true
354 else
355 options[:oauth_consumer_key] && options[:oauth_consumer_secret] &&
356 options[:method] && options[:uri]
357 end
358 end
359
360 def usage
361 stdout.puts option_parser.help
362 stdout.puts
363 stdout.puts "Available commands:"
364 SUPPORTED_COMMANDS.each do |command, desc|
365 puts " #{command.ljust(15)}#{desc}"
366 end
367 end
368
369 def valid_command?
370 SUPPORTED_COMMANDS.keys.include?(command)
371 end
372
373 def verbose?
374 options[:verbose]
375 end
376 end
377 end
+0
-65
vendor/oauth/client/action_controller_request.rb less more
0 require 'oauth/client/helper'
1 if defined? ActionDispatch
2 require 'oauth/request_proxy/rack_request'
3 require 'action_dispatch/testing/test_process'
4 else
5 require 'oauth/request_proxy/action_controller_request'
6 require 'action_controller/test_process'
7 end
8
9 module ActionController
10 class Base
11 if defined? ActionDispatch
12 def process_with_new_base_test(request, response=nil)
13 request.apply_oauth! if request.respond_to?(:apply_oauth!)
14 super(request, response)
15 end
16 else
17 def process_with_oauth(request, response=nil)
18 request.apply_oauth! if request.respond_to?(:apply_oauth!)
19 process_without_oauth(request, response)
20 end
21 alias_method_chain :process, :oauth
22 end
23 end
24
25 class TestRequest
26 def self.use_oauth=(bool)
27 @use_oauth = bool
28 end
29
30 def self.use_oauth?
31 @use_oauth
32 end
33
34 def configure_oauth(consumer = nil, token = nil, options = {})
35 @oauth_options = { :consumer => consumer,
36 :token => token,
37 :scheme => 'header',
38 :signature_method => nil,
39 :nonce => nil,
40 :timestamp => nil }.merge(options)
41 end
42
43 def apply_oauth!
44 return unless ActionController::TestRequest.use_oauth? && @oauth_options
45
46 @oauth_helper = OAuth::Client::Helper.new(self, @oauth_options.merge(:request_uri => (respond_to?(:fullpath) ? fullpath : request_uri)))
47 @oauth_helper.amend_user_agent_header(env)
48
49 self.send("set_oauth_#{@oauth_options[:scheme]}")
50 end
51
52 def set_oauth_header
53 env['Authorization'] = @oauth_helper.header
54 end
55
56 def set_oauth_parameters
57 @query_parameters = @oauth_helper.parameters_with_oauth
58 @query_parameters.merge!(:oauth_signature => @oauth_helper.signature)
59 end
60
61 def set_oauth_query_string
62 end
63 end
64 end
+0
-120
vendor/oauth/client/em_http.rb less more
0 require 'em-http'
1 require 'oauth/helper'
2 require 'oauth/client/helper'
3 require 'oauth/request_proxy/em_http_request'
4
5 # Extensions for em-http so that we can use consumer.sign! with an EventMachine::HttpClient
6 # instance. This is purely syntactic sugar.
7 class EventMachine::HttpClient
8
9 attr_reader :oauth_helper
10
11 # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
12 # this may add a header, additional query string parameters, or additional POST body parameters.
13 # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
14 # header.
15 #
16 # * http - Configured Net::HTTP instance, ignored in this scenario except for getting host.
17 # * consumer - OAuth::Consumer instance
18 # * token - OAuth::Token instance
19 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
20 # +signature_method+, +nonce+, +timestamp+)
21 #
22 # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
23 #
24 # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1]
25 def oauth!(http, consumer = nil, token = nil, options = {})
26 options = { :request_uri => normalized_oauth_uri(http),
27 :consumer => consumer,
28 :token => token,
29 :scheme => 'header',
30 :signature_method => nil,
31 :nonce => nil,
32 :timestamp => nil }.merge(options)
33
34 @oauth_helper = OAuth::Client::Helper.new(self, options)
35 self.__send__(:"set_oauth_#{options[:scheme]}")
36 end
37
38 # Create a string suitable for signing for an HTTP request. This process involves parameter
39 # normalization as specified in the OAuth specification. The exact normalization also depends
40 # on the <tt>options[:scheme]</tt> being used so this must match what will be used for the request
41 # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
42 # header.
43 #
44 # * http - Configured Net::HTTP instance
45 # * consumer - OAuth::Consumer instance
46 # * token - OAuth::Token instance
47 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
48 # +signature_method+, +nonce+, +timestamp+)
49 #
50 # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
51 def signature_base_string(http, consumer = nil, token = nil, options = {})
52 options = { :request_uri => normalized_oauth_uri(http),
53 :consumer => consumer,
54 :token => token,
55 :scheme => 'header',
56 :signature_method => nil,
57 :nonce => nil,
58 :timestamp => nil }.merge(options)
59
60 OAuth::Client::Helper.new(self, options).signature_base_string
61 end
62
63 # This code was lifted from the em-http-request because it was removed from
64 # the gem June 19, 2010
65 # see: http://github.com/igrigorik/em-http-request/commit/d536fc17d56dbe55c487eab01e2ff9382a62598b
66 def normalize_uri
67 @normalized_uri ||= begin
68 uri = @uri.dup
69 encoded_query = encode_query(@uri, @options[:query])
70 path, query = encoded_query.split("?", 2)
71 uri.query = query unless encoded_query.empty?
72 uri.path = path
73 uri
74 end
75 end
76
77 protected
78
79 def combine_query(path, query, uri_query)
80 combined_query = if query.kind_of?(Hash)
81 query.map { |k, v| encode_param(k, v) }.join('&')
82 else
83 query.to_s
84 end
85 if !uri_query.to_s.empty?
86 combined_query = [combined_query, uri_query].reject {|part| part.empty?}.join("&")
87 end
88 combined_query.to_s.empty? ? path : "#{path}?#{combined_query}"
89 end
90
91 # Since we expect to get the host etc details from the http instance (...),
92 # we create a fake url here. Surely this is a horrible, horrible idea?
93 def normalized_oauth_uri(http)
94 uri = URI.parse(normalize_uri.path)
95 uri.host = http.address
96 uri.port = http.port
97
98 if http.respond_to?(:use_ssl?) && http.use_ssl?
99 uri.scheme = "https"
100 else
101 uri.scheme = "http"
102 end
103 uri.to_s
104 end
105
106 def set_oauth_header
107 headers = (self.options[:head] ||= {})
108 headers['Authorization'] = @oauth_helper.header
109 end
110
111 def set_oauth_body
112 raise NotImplementedError, 'please use the set_oauth_header method instead'
113 end
114
115 def set_oauth_query_string
116 raise NotImplementedError, 'please use the set_oauth_header method instead'
117 end
118
119 end
+0
-91
vendor/oauth/client/helper.rb less more
0 require 'oauth/client'
1 require 'oauth/consumer'
2 require 'oauth/helper'
3 require 'oauth/token'
4 require 'oauth/signature/hmac/sha1'
5
6 module OAuth::Client
7 class Helper
8 include OAuth::Helper
9
10 def initialize(request, options = {})
11 @request = request
12 @options = options
13 @options[:signature_method] ||= 'HMAC-SHA1'
14 end
15
16 def options
17 @options
18 end
19
20 def nonce
21 options[:nonce] ||= generate_key
22 end
23
24 def timestamp
25 options[:timestamp] ||= generate_timestamp
26 end
27
28 def oauth_parameters
29 {
30 'oauth_body_hash' => options[:body_hash],
31 'oauth_callback' => options[:oauth_callback],
32 'oauth_consumer_key' => options[:consumer].key,
33 'oauth_token' => options[:token] ? options[:token].token : '',
34 'oauth_signature_method' => options[:signature_method],
35 'oauth_timestamp' => timestamp,
36 'oauth_nonce' => nonce,
37 'oauth_verifier' => options[:oauth_verifier],
38 'oauth_version' => (options[:oauth_version] || '1.0'),
39 'oauth_session_handle' => options[:oauth_session_handle]
40 }.reject { |k,v| v.to_s == "" }
41 end
42
43 def signature(extra_options = {})
44 OAuth::Signature.sign(@request, { :uri => options[:request_uri],
45 :consumer => options[:consumer],
46 :token => options[:token],
47 :unsigned_parameters => options[:unsigned_parameters]
48 }.merge(extra_options) )
49 end
50
51 def signature_base_string(extra_options = {})
52 OAuth::Signature.signature_base_string(@request, { :uri => options[:request_uri],
53 :consumer => options[:consumer],
54 :token => options[:token],
55 :parameters => oauth_parameters}.merge(extra_options) )
56 end
57
58 def hash_body
59 @options[:body_hash] = OAuth::Signature.body_hash(@request, :parameters => oauth_parameters)
60 end
61
62 def amend_user_agent_header(headers)
63 @oauth_ua_string ||= "OAuth gem v#{OAuth::VERSION}"
64 # Net::HTTP in 1.9 appends Ruby
65 if headers['User-Agent'] && headers['User-Agent'] != 'Ruby'
66 headers['User-Agent'] += " (#{@oauth_ua_string})"
67 else
68 headers['User-Agent'] = @oauth_ua_string
69 end
70 end
71
72 def header
73 parameters = oauth_parameters
74 parameters.merge!('oauth_signature' => signature(options.merge(:parameters => parameters)))
75
76 header_params_str = parameters.sort.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
77
78 realm = "realm=\"#{options[:realm]}\", " if options[:realm]
79 "OAuth #{realm}#{header_params_str}"
80 end
81
82 def parameters
83 OAuth::RequestProxy.proxy(@request).parameters
84 end
85
86 def parameters_with_oauth
87 oauth_parameters.merge(parameters)
88 end
89 end
90 end
+0
-120
vendor/oauth/client/net_http.rb less more
0 require 'oauth/helper'
1 require 'oauth/client/helper'
2 require 'oauth/request_proxy/net_http'
3
4 class Net::HTTPGenericRequest
5 include OAuth::Helper
6
7 attr_reader :oauth_helper
8
9 # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
10 # this may add a header, additional query string parameters, or additional POST body parameters.
11 # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
12 # header.
13 #
14 # * http - Configured Net::HTTP instance
15 # * consumer - OAuth::Consumer instance
16 # * token - OAuth::Token instance
17 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
18 # +signature_method+, +nonce+, +timestamp+)
19 #
20 # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
21 #
22 # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1],
23 # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html]
24 def oauth!(http, consumer = nil, token = nil, options = {})
25 helper_options = oauth_helper_options(http, consumer, token, options)
26 @oauth_helper = OAuth::Client::Helper.new(self, helper_options)
27 @oauth_helper.amend_user_agent_header(self)
28 @oauth_helper.hash_body if oauth_body_hash_required?
29 self.send("set_oauth_#{helper_options[:scheme]}")
30 end
31
32 # Create a string suitable for signing for an HTTP request. This process involves parameter
33 # normalization as specified in the OAuth specification. The exact normalization also depends
34 # on the <tt>options[:scheme]</tt> being used so this must match what will be used for the request
35 # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
36 # header.
37 #
38 # * http - Configured Net::HTTP instance
39 # * consumer - OAuth::Consumer instance
40 # * token - OAuth::Token instance
41 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
42 # +signature_method+, +nonce+, +timestamp+)
43 #
44 # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1],
45 # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html]
46 def signature_base_string(http, consumer = nil, token = nil, options = {})
47 helper_options = oauth_helper_options(http, consumer, token, options)
48 oauth_helper = OAuth::Client::Helper.new(self, helper_options)
49 oauth_helper.hash_body if oauth_body_hash_required?
50 oauth_helper.signature_base_string
51 end
52
53 private
54
55 def oauth_helper_options(http, consumer, token, options)
56 { :request_uri => oauth_full_request_uri(http,options),
57 :consumer => consumer,
58 :token => token,
59 :scheme => 'header',
60 :signature_method => nil,
61 :nonce => nil,
62 :timestamp => nil }.merge(options)
63 end
64
65 def oauth_full_request_uri(http,options)
66 uri = URI.parse(self.path)
67 uri.host = http.address
68 uri.port = http.port
69
70 if options[:request_endpoint] && options[:site]
71 is_https = options[:site].match(%r(^https://))
72 uri.host = options[:site].gsub(%r(^https?://), '')
73 uri.port ||= is_https ? 443 : 80
74 end
75
76 if http.respond_to?(:use_ssl?) && http.use_ssl?
77 uri.scheme = "https"
78 else
79 uri.scheme = "http"
80 end
81
82 uri.to_s
83 end
84
85 def oauth_body_hash_required?
86 request_body_permitted? && !content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
87 end
88
89 def set_oauth_header
90 self['Authorization'] = @oauth_helper.header
91 end
92
93 # FIXME: if you're using a POST body and query string parameters, this method
94 # will move query string parameters into the body unexpectedly. This may
95 # cause problems with non-x-www-form-urlencoded bodies submitted to URLs
96 # containing query string params. If duplicate parameters are present in both
97 # places, all instances should be included when calculating the signature
98 # base string.
99
100 def set_oauth_body
101 self.set_form_data(@oauth_helper.stringify_keys(@oauth_helper.parameters_with_oauth))
102 params_with_sig = @oauth_helper.parameters.merge(:oauth_signature => @oauth_helper.signature)
103 self.set_form_data(@oauth_helper.stringify_keys(params_with_sig))
104 end
105
106 def set_oauth_query_string
107 oauth_params_str = @oauth_helper.oauth_parameters.map { |k,v| [escape(k), escape(v)] * "=" }.join("&")
108 uri = URI.parse(path)
109 if uri.query.to_s == ""
110 uri.query = oauth_params_str
111 else
112 uri.query = uri.query + "&" + oauth_params_str
113 end
114
115 @path = uri.to_s
116
117 @path << "&oauth_signature=#{escape(oauth_helper.signature)}"
118 end
119 end
+0
-4
vendor/oauth/client.rb less more
0 module OAuth
1 module Client
2 end
3 end
+0
-389
vendor/oauth/consumer.rb less more
0 require 'net/http'
1 require 'net/https'
2 require 'oauth/oauth'
3 require 'oauth/client/net_http'
4 require 'oauth/errors'
5 require 'cgi'
6
7 module OAuth
8 class Consumer
9 # determine the certificate authority path to verify SSL certs
10 CA_FILES = %w(/etc/ssl/certs/ca-certificates.crt /usr/share/curl/curl-ca-bundle.crt)
11 CA_FILES.each do |ca_file|
12 if File.exists?(ca_file)
13 CA_FILE = ca_file
14 break
15 end
16 end
17 CA_FILE = nil unless defined?(CA_FILE)
18
19 @@default_options = {
20 # Signature method used by server. Defaults to HMAC-SHA1
21 :signature_method => 'HMAC-SHA1',
22
23 # default paths on site. These are the same as the defaults set up by the generators
24 :request_token_path => '/oauth/request_token',
25 :authorize_path => '/oauth/authorize',
26 :access_token_path => '/oauth/access_token',
27
28 :proxy => nil,
29 # How do we send the oauth values to the server see
30 # http://oauth.net/core/1.0/#consumer_req_param for more info
31 #
32 # Possible values:
33 #
34 # :header - via the Authorize header (Default) ( option 1. in spec)
35 # :body - url form encoded in body of POST request ( option 2. in spec)
36 # :query_string - via the query part of the url ( option 3. in spec)
37 :scheme => :header,
38
39 # Default http method used for OAuth Token Requests (defaults to :post)
40 :http_method => :post,
41
42 # Add a custom ca_file for consumer
43 # :ca_file => '/etc/certs.pem'
44
45 :oauth_version => "1.0"
46 }
47
48 attr_accessor :options, :key, :secret
49 attr_writer :site, :http
50
51 # Create a new consumer instance by passing it a configuration hash:
52 #
53 # @consumer = OAuth::Consumer.new(key, secret, {
54 # :site => "http://term.ie",
55 # :scheme => :header,
56 # :http_method => :post,
57 # :request_token_path => "/oauth/example/request_token.php",
58 # :access_token_path => "/oauth/example/access_token.php",
59 # :authorize_path => "/oauth/example/authorize.php"
60 # })
61 #
62 # Start the process by requesting a token
63 #
64 # @request_token = @consumer.get_request_token
65 # session[:request_token] = @request_token
66 # redirect_to @request_token.authorize_url
67 #
68 # When user returns create an access_token
69 #
70 # @access_token = @request_token.get_access_token
71 # @photos=@access_token.get('/photos.xml')
72 #
73 def initialize(consumer_key, consumer_secret, options = {})
74 @key = consumer_key
75 @secret = consumer_secret
76
77 # ensure that keys are symbols
78 @options = @@default_options.merge(options.inject({}) do |opts, (key, value)|
79 opts[key.to_sym] = value
80 opts
81 end)
82 end
83
84 # The default http method
85 def http_method
86 @http_method ||= @options[:http_method] || :post
87 end
88
89 # The HTTP object for the site. The HTTP Object is what you get when you do Net::HTTP.new
90 def http
91 @http ||= create_http
92 end
93
94 # Contains the root URI for this site
95 def uri(custom_uri = nil)
96 if custom_uri
97 @uri = custom_uri
98 @http = create_http # yike, oh well. less intrusive this way
99 else # if no custom passed, we use existing, which, if unset, is set to site uri
100 @uri ||= URI.parse(site)
101 end
102 end
103
104 def get_access_token(request_token, request_options = {}, *arguments, &block)
105 response = token_request(http_method, (access_token_url? ? access_token_url : access_token_path), request_token, request_options, *arguments, &block)
106 OAuth::AccessToken.from_hash(self, response)
107 end
108
109 # Makes a request to the service for a new OAuth::RequestToken
110 #
111 # @request_token = @consumer.get_request_token
112 #
113 # To include OAuth parameters:
114 #
115 # @request_token = @consumer.get_request_token \
116 # :oauth_callback => "http://example.com/cb"
117 #
118 # To include application-specific parameters:
119 #
120 # @request_token = @consumer.get_request_token({}, :foo => "bar")
121 #
122 # TODO oauth_callback should be a mandatory parameter
123 def get_request_token(request_options = {}, *arguments, &block)
124 # if oauth_callback wasn't provided, it is assumed that oauth_verifiers
125 # will be exchanged out of band
126 request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND unless request_options[:exclude_callback]
127
128 if block_given?
129 response = token_request(http_method,
130 (request_token_url? ? request_token_url : request_token_path),
131 nil,
132 request_options,
133 *arguments, &block)
134 else
135 response = token_request(http_method, (request_token_url? ? request_token_url : request_token_path), nil, request_options, *arguments)
136 end
137 OAuth::RequestToken.from_hash(self, response)
138 end
139
140 # Creates, signs and performs an http request.
141 # It's recommended to use the OAuth::Token classes to set this up correctly.
142 # request_options take precedence over consumer-wide options when signing
143 # a request.
144 # arguments are POST and PUT bodies (a Hash, string-encoded parameters, or
145 # absent), followed by additional HTTP headers.
146 #
147 # @consumer.request(:get, '/people', @token, { :scheme => :query_string })
148 # @consumer.request(:post, '/people', @token, {}, @person.to_xml, { 'Content-Type' => 'application/xml' })
149 #
150 def request(http_method, path, token = nil, request_options = {}, *arguments)
151 if path !~ /^\//
152 @http = create_http(path)
153 _uri = URI.parse(path)
154 path = "#{_uri.path}#{_uri.query ? "?#{_uri.query}" : ""}"
155 end
156
157 # override the request with your own, this is useful for file uploads which Net::HTTP does not do
158 req = create_signed_request(http_method, path, token, request_options, *arguments)
159 return nil if block_given? and yield(req) == :done
160 rsp = http.request(req)
161 # check for an error reported by the Problem Reporting extension
162 # (http://wiki.oauth.net/ProblemReporting)
163 # note: a 200 may actually be an error; check for an oauth_problem key to be sure
164 if !(headers = rsp.to_hash["www-authenticate"]).nil? &&
165 (h = headers.select { |hdr| hdr =~ /^OAuth / }).any? &&
166 h.first =~ /oauth_problem/
167
168 # puts "Header: #{h.first}"
169
170 # TODO doesn't handle broken responses from api.login.yahoo.com
171 # remove debug code when done
172 params = OAuth::Helper.parse_header(h.first)
173
174 # puts "Params: #{params.inspect}"
175 # puts "Body: #{rsp.body}"
176
177 raise OAuth::Problem.new(params.delete("oauth_problem"), rsp, params)
178 end
179
180 rsp
181 end
182
183 # Creates and signs an http request.
184 # It's recommended to use the Token classes to set this up correctly
185 def create_signed_request(http_method, path, token = nil, request_options = {}, *arguments)
186 request = create_http_request(http_method, path, *arguments)
187 sign!(request, token, request_options)
188 request
189 end
190
191 # Creates a request and parses the result as url_encoded. This is used internally for the RequestToken and AccessToken requests.
192 def token_request(http_method, path, token = nil, request_options = {}, *arguments)
193 response = request(http_method, path, token, request_options, *arguments)
194 case response.code.to_i
195
196 when (200..299)
197 if block_given?
198 yield response.body
199 else
200 # symbolize keys
201 # TODO this could be considered unexpected behavior; symbols or not?
202 # TODO this also drops subsequent values from multi-valued keys
203 CGI.parse(response.body).inject({}) do |h,(k,v)|
204 h[k.strip.to_sym] = v.first
205 h[k.strip] = v.first
206 h
207 end
208 end
209 when (300..399)
210 # this is a redirect
211 uri = URI.parse(response.header['location'])
212 response.error! if uri.path == path # careful of those infinite redirects
213 self.token_request(http_method, uri.path, token, request_options, arguments)
214 when (400..499)
215 raise OAuth::Unauthorized, response
216 else
217 response.error!
218 end
219 end
220
221 # Sign the Request object. Use this if you have an externally generated http request object you want to sign.
222 def sign!(request, token = nil, request_options = {})
223 request.oauth!(http, self, token, options.merge(request_options))
224 end
225
226 # Return the signature_base_string
227 def signature_base_string(request, token = nil, request_options = {})
228 request.signature_base_string(http, self, token, options.merge(request_options))
229 end
230
231 def site
232 @options[:site].to_s
233 end
234
235 def request_endpoint
236 return nil if @options[:request_endpoint].nil?
237 @options[:request_endpoint].to_s
238 end
239
240 def scheme
241 @options[:scheme]
242 end
243
244 def request_token_path
245 @options[:request_token_path]
246 end
247
248 def authorize_path
249 @options[:authorize_path]
250 end
251
252 def access_token_path
253 @options[:access_token_path]
254 end
255
256 # TODO this is ugly, rewrite
257 def request_token_url
258 @options[:request_token_url] || site + request_token_path
259 end
260
261 def request_token_url?
262 @options.has_key?(:request_token_url)
263 end
264
265 def authorize_url
266 @options[:authorize_url] || site + authorize_path
267 end
268
269 def authorize_url?
270 @options.has_key?(:authorize_url)
271 end
272
273 def access_token_url
274 @options[:access_token_url] || site + access_token_path
275 end
276
277 def access_token_url?
278 @options.has_key?(:access_token_url)
279 end
280
281 def proxy
282 @options[:proxy]
283 end
284
285 protected
286
287 # Instantiates the http object
288 def create_http(_url = nil)
289
290
291 if !request_endpoint.nil?
292 _url = request_endpoint
293 end
294
295
296 if _url.nil? || _url[0] =~ /^\//
297 our_uri = URI.parse(site)
298 else
299 our_uri = URI.parse(_url)
300 end
301
302
303 if proxy.nil?
304 http_object = Net::HTTP.new(our_uri.host, our_uri.port)
305 else
306 proxy_uri = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
307 http_object = Net::HTTP.new(our_uri.host, our_uri.port, proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
308 end
309
310 http_object.use_ssl = (our_uri.scheme == 'https')
311
312 if @options[:ca_file] || CA_FILE
313 http_object.ca_file = @options[:ca_file] || CA_FILE
314 http_object.verify_mode = OpenSSL::SSL::VERIFY_PEER
315 http_object.verify_depth = 5
316 else
317 http_object.verify_mode = OpenSSL::SSL::VERIFY_NONE
318 end
319
320 http_object.read_timeout = http_object.open_timeout = @options[:timeout] || 30
321 http_object.open_timeout = @options[:open_timeout] if @options[:open_timeout]
322
323 http_object
324 end
325
326 # create the http request object for a given http_method and path
327 def create_http_request(http_method, path, *arguments)
328 http_method = http_method.to_sym
329
330 if [:post, :put].include?(http_method)
331 data = arguments.shift
332 end
333
334 # if the base site contains a path, add it now
335 uri = URI.parse(site)
336 path = uri.path + path if uri.path && uri.path != '/'
337
338 headers = arguments.first.is_a?(Hash) ? arguments.shift : {}
339
340 case http_method
341 when :post
342 request = Net::HTTP::Post.new(path,headers)
343 request["Content-Length"] = '0' # Default to 0
344 when :put
345 request = Net::HTTP::Put.new(path,headers)
346 request["Content-Length"] = '0' # Default to 0
347 when :get
348 request = Net::HTTP::Get.new(path,headers)
349 when :delete
350 request = Net::HTTP::Delete.new(path,headers)
351 when :head
352 request = Net::HTTP::Head.new(path,headers)
353 else
354 raise ArgumentError, "Don't know how to handle http_method: :#{http_method.to_s}"
355 end
356
357 if data.is_a?(Hash)
358 request.body = OAuth::Helper.normalize(data)
359 request.content_type = 'application/x-www-form-urlencoded'
360 elsif data
361 if data.respond_to?(:read)
362 request.body_stream = data
363 if data.respond_to?(:length)
364 request["Content-Length"] = data.length.to_s
365 elsif data.respond_to?(:stat) && data.stat.respond_to?(:size)
366 request["Content-Length"] = data.stat.size.to_s
367 else
368 raise ArgumentError, "Don't know how to send a body_stream that doesn't respond to .length or .stat.size"
369 end
370 else
371 request.body = data.to_s
372 request["Content-Length"] = request.body.length.to_s
373 end
374 end
375
376 request
377 end
378
379 def marshal_dump(*args)
380 {:key => @key, :secret => @secret, :options => @options}
381 end
382
383 def marshal_load(data)
384 initialize(data[:key], data[:secret], data[:options])
385 end
386
387 end
388 end
+0
-31
vendor/oauth/core_ext.rb less more
0 # these are to backport methods from 1.8.7/1.9.1 to 1.8.6
1
2 class Object
3
4 unless method_defined?(:tap)
5 def tap
6 yield self
7 self
8 end
9 end
10
11 end
12
13 class String
14
15
16
17 unless method_defined?(:bytesize)
18 def bytesize
19 self.size
20 end
21 end
22
23 unless method_defined?(:bytes)
24 def bytes
25 require 'enumerator'
26 Enumerable::Enumerator.new(self, :each_byte)
27 end
28 end
29
30 end
+0
-4
vendor/oauth/errors/error.rb less more
0 module OAuth
1 class Error < StandardError
2 end
3 end
+0
-14
vendor/oauth/errors/problem.rb less more
0 module OAuth
1 class Problem < OAuth::Unauthorized
2 attr_reader :problem, :params
3 def initialize(problem, request = nil, params = {})
4 super(request)
5 @problem = problem
6 @params = params
7 end
8
9 def to_s
10 problem
11 end
12 end
13 end
+0
-12
vendor/oauth/errors/unauthorized.rb less more
0 module OAuth
1 class Unauthorized < OAuth::Error
2 attr_reader :request
3 def initialize(request = nil)
4 @request = request
5 end
6
7 def to_s
8 [request.code, request.message] * " "
9 end
10 end
11 end
+0
-3
vendor/oauth/errors.rb less more
0 require 'oauth/errors/error'
1 require 'oauth/errors/unauthorized'
2 require 'oauth/errors/problem'
+0
-109
vendor/oauth/helper.rb less more
0 require 'openssl'
1 require 'base64'
2
3 module OAuth
4 module Helper
5 extend self
6
7 # Escape +value+ by URL encoding all non-reserved character.
8 #
9 # See Also: {OAuth core spec version 1.0, section 5.1}[http://oauth.net/core/1.0#rfc.section.5.1]
10 def escape(value)
11 URI::escape(value.to_s, OAuth::RESERVED_CHARACTERS)
12 rescue ArgumentError
13 URI::escape(value.to_s.force_encoding(Encoding::UTF_8), OAuth::RESERVED_CHARACTERS)
14 end
15
16 # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word
17 # characters removed.
18 def generate_key(size=32)
19 Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
20 end
21
22 alias_method :generate_nonce, :generate_key
23
24 def generate_timestamp #:nodoc:
25 Time.now.to_i.to_s
26 end
27
28 # Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical
29 # byte value ordering. If two or more parameters share the same name, they are sorted by their value.
30 # Parameters are concatenated in their sorted order into a single string. For each parameter, the name
31 # is separated from the corresponding value by an "=" character, even if the value is empty. Each
32 # name-value pair is separated by an "&" character.
33 #
34 # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
35 def normalize(params)
36 params.sort.map do |k, values|
37 if values.is_a?(Array)
38 # make sure the array has an element so we don't lose the key
39 values << nil if values.empty?
40 # multiple values were provided for a single key
41 values.sort.collect do |v|
42 [escape(k),escape(v)] * "="
43 end
44 elsif values.is_a?(Hash)
45 normalize_nested_query(values, k)
46 else
47 [escape(k),escape(values)] * "="
48 end
49 end * "&"
50 end
51
52 #Returns a string representation of the Hash like in URL query string
53 # build_nested_query({:level_1 => {:level_2 => ['value_1','value_2']}}, 'prefix'))
54 # #=> ["prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_1", "prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_2"]
55 def normalize_nested_query(value, prefix = nil)
56 case value
57 when Array
58 value.map do |v|
59 normalize_nested_query(v, "#{prefix}[]")
60 end.flatten.sort
61 when Hash
62 value.map do |k, v|
63 normalize_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
64 end.flatten.sort
65 else
66 [escape(prefix), escape(value)] * "="
67 end
68 end
69
70 # Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and
71 # removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a
72 # valid hash. Does not validate the keys or values.
73 #
74 # hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate'])
75 # hash['oauth_timestamp']
76 # #=>"1234567890"
77 #
78 def parse_header(header)
79 # decompose
80 params = header[6,header.length].split(/[,=&]/)
81
82 # odd number of arguments - must be a malformed header.
83 raise OAuth::Problem.new("Invalid authorization header") if params.size % 2 != 0
84
85 params.map! do |v|
86 # strip and unescape
87 val = unescape(v.strip)
88 # strip quotes
89 val.sub(/^\"(.*)\"$/, '\1')
90 end
91
92 # convert into a Hash
93 Hash[*params.flatten]
94 end
95
96 def unescape(value)
97 URI.unescape(value.gsub('+', '%2B'))
98 end
99
100 def stringify_keys(hash)
101 new_h = {}
102 hash.each do |k, v|
103 new_h[k.to_s] = v.is_a?(Hash) ? stringify_keys(v) : v
104 end
105 new_h
106 end
107 end
108 end
+0
-13
vendor/oauth/oauth.rb less more
0 module OAuth
1 # request tokens are passed between the consumer and the provider out of
2 # band (i.e. callbacks cannot be used), per section 6.1.1
3 OUT_OF_BAND = "oob"
4
5 # required parameters, per sections 6.1.1, 6.3.1, and 7
6 PARAMETERS = %w(oauth_callback oauth_consumer_key oauth_token
7 oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier
8 oauth_version oauth_signature oauth_body_hash)
9
10 # reserved character regexp, per section 5.1
11 RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~]/
12 end
+0
-25
vendor/oauth/oauth_test_helper.rb less more
0 require 'action_controller'
1 require 'action_controller/test_process'
2
3 module OAuth
4 module OAuthTestHelper
5 def mock_incoming_request_with_query(request)
6 incoming = ActionController::TestRequest.new(request.to_hash)
7 incoming.request_uri = request.path
8 incoming.host = request.uri.host
9 incoming.env["SERVER_PORT"] = request.uri.port
10 incoming.env['REQUEST_METHOD'] = request.http_method
11 incoming
12 end
13
14 def mock_incoming_request_with_authorize_header(request)
15 incoming = ActionController::TestRequest.new
16 incoming.request_uri = request.path
17 incoming.host = request.uri.host
18 incoming.env["HTTP_AUTHORIZATION"] = request.to_auth_string
19 incoming.env["SERVER_PORT"] = request.uri.port
20 incoming.env['REQUEST_METHOD'] = request.http_method
21 incoming
22 end
23 end
24 end
+0
-62
vendor/oauth/request_proxy/action_controller_request.rb less more
0 require 'active_support'
1 require 'action_controller'
2 require 'action_controller/request'
3 require 'uri'
4
5 module OAuth::RequestProxy
6 class ActionControllerRequest < OAuth::RequestProxy::Base
7 proxies(defined?(ActionController::AbstractRequest) ? ActionController::AbstractRequest : ActionController::Request)
8
9 def method
10 request.method.to_s.upcase
11 end
12
13 def uri
14 request.url
15 end
16
17 def parameters
18 if options[:clobber_request]
19 options[:parameters] || {}
20 else
21 params = request_params.merge(query_params).merge(header_params)
22 params.stringify_keys! if params.respond_to?(:stringify_keys!)
23 params.merge(options[:parameters] || {})
24 end
25 end
26
27 # Override from OAuth::RequestProxy::Base to avoid roundtrip
28 # conversion to Hash or Array and thus preserve the original
29 # parameter names
30 def parameters_for_signature
31 params = []
32 params << options[:parameters].to_query if options[:parameters]
33
34 unless options[:clobber_request]
35 params << header_params.to_query
36 params << request.query_string unless query_string_blank?
37
38 if request.post? && request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
39 params << request.raw_post
40 end
41 end
42
43 params.
44 join('&').split('&').
45 reject(&:blank?).
46 map { |p| p.split('=').map{|esc| CGI.unescape(esc)} }.
47 reject { |kv| kv[0] == 'oauth_signature'}
48 end
49
50 protected
51
52 def query_params
53 request.query_parameters
54 end
55
56 def request_params
57 request.request_parameters
58 end
59
60 end
61 end
+0
-174
vendor/oauth/request_proxy/base.rb less more
0 require 'oauth/request_proxy'
1 require 'oauth/helper'
2
3 module OAuth::RequestProxy
4 class Base
5 include OAuth::Helper
6
7 def self.proxies(klass)
8 OAuth::RequestProxy.available_proxies[klass] = self
9 end
10
11 attr_accessor :request, :options, :unsigned_parameters
12
13 def initialize(request, options = {})
14 @request = request
15 @unsigned_parameters = (options[:unsigned_parameters] || []).map {|param| param.to_s}
16 @options = options
17 end
18
19 ## OAuth parameters
20
21 def oauth_callback
22 parameters['oauth_callback']
23 end
24
25 def oauth_consumer_key
26 parameters['oauth_consumer_key']
27 end
28
29 def oauth_nonce
30 parameters['oauth_nonce']
31 end
32
33 def oauth_signature
34 # TODO can this be nil?
35 [parameters['oauth_signature']].flatten.first || ""
36 end
37
38 def oauth_signature_method
39 case parameters['oauth_signature_method']
40 when Array
41 parameters['oauth_signature_method'].first
42 else
43 parameters['oauth_signature_method']
44 end
45 end
46
47 def oauth_timestamp
48 parameters['oauth_timestamp']
49 end
50
51 def oauth_token
52 parameters['oauth_token']
53 end
54
55 def oauth_verifier
56 parameters['oauth_verifier']
57 end
58
59 def oauth_version
60 parameters["oauth_version"]
61 end
62
63 # TODO deprecate these
64 alias_method :consumer_key, :oauth_consumer_key
65 alias_method :token, :oauth_token
66 alias_method :nonce, :oauth_nonce
67 alias_method :timestamp, :oauth_timestamp
68 alias_method :signature, :oauth_signature
69 alias_method :signature_method, :oauth_signature_method
70
71 ## Parameter accessors
72
73 def parameters
74 raise NotImplementedError, "Must be implemented by subclasses"
75 end
76
77 def parameters_for_signature
78 parameters.reject { |k,v| k == "oauth_signature" || unsigned_parameters.include?(k)}
79 end
80
81 def oauth_parameters
82 parameters.select { |k,v| OAuth::PARAMETERS.include?(k) }.reject { |k,v| v == "" }
83 end
84
85 def non_oauth_parameters
86 parameters.reject { |k,v| OAuth::PARAMETERS.include?(k) }
87 end
88
89 # See 9.1.2 in specs
90 def normalized_uri
91 u = URI.parse(uri)
92 "#{u.scheme.downcase}://#{u.host.downcase}#{(u.scheme.downcase == 'http' && u.port != 80) || (u.scheme.downcase == 'https' && u.port != 443) ? ":#{u.port}" : ""}#{(u.path && u.path != '') ? u.path : '/'}"
93 end
94
95 # See 9.1.1. in specs Normalize Request Parameters
96 def normalized_parameters
97 normalize(parameters_for_signature)
98 end
99
100 def sign(options = {})
101 OAuth::Signature.sign(self, options)
102 end
103
104 def sign!(options = {})
105 parameters["oauth_signature"] = sign(options)
106 @signed = true
107 signature
108 end
109
110 # See 9.1 in specs
111 def signature_base_string
112 base = [method, normalized_uri, normalized_parameters]
113 base.map { |v| escape(v) }.join("&")
114 end
115
116 # Has this request been signed yet?
117 def signed?
118 @signed
119 end
120
121 # URI, including OAuth parameters
122 def signed_uri(with_oauth = true)
123 if signed?
124 if with_oauth
125 params = parameters
126 else
127 params = non_oauth_parameters
128 end
129
130 [uri, normalize(params)] * "?"
131 else
132 STDERR.puts "This request has not yet been signed!"
133 end
134 end
135
136 # Authorization header for OAuth
137 def oauth_header(options = {})
138 header_params_str = oauth_parameters.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
139
140 realm = "realm=\"#{options[:realm]}\", " if options[:realm]
141 "OAuth #{realm}#{header_params_str}"
142 end
143
144 def query_string_blank?
145 if uri = request.request_uri
146 uri.split('?', 2)[1].nil?
147 else
148 request.query_string.blank?
149 end
150 end
151
152 protected
153
154 def header_params
155 %w( X-HTTP_AUTHORIZATION Authorization HTTP_AUTHORIZATION ).each do |header|
156 next unless request.env.include?(header)
157
158 header = request.env[header]
159 next unless header[0,6] == 'OAuth '
160
161 # parse the header into a Hash
162 oauth_params = OAuth::Helper.parse_header(header)
163
164 # remove non-OAuth parameters
165 oauth_params.reject! { |k,v| k !~ /^oauth_/ }
166
167 return oauth_params
168 end
169
170 return {}
171 end
172 end
173 end
+0
-55
vendor/oauth/request_proxy/curb_request.rb less more
0 require 'oauth/request_proxy/base'
1 require 'curb'
2 require 'uri'
3 require 'cgi'
4
5 module OAuth::RequestProxy::Curl
6 class Easy < OAuth::RequestProxy::Base
7 # Proxy for signing Curl::Easy requests
8 # Usage example:
9 # oauth_params = {:consumer => oauth_consumer, :token => access_token}
10 # req = Curl::Easy.new(uri)
11 # oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri))
12 # req.headers.merge!({"Authorization" => oauth_helper.header})
13 # req.http_get
14 # response = req.body_str
15 proxies ::Curl::Easy
16
17 def method
18 nil
19 end
20
21 def uri
22 options[:uri].to_s
23 end
24
25 def parameters
26 if options[:clobber_request]
27 options[:parameters]
28 else
29 post_parameters.merge(query_parameters).merge(options[:parameters] || {})
30 end
31 end
32
33 private
34
35 def query_parameters
36 query = URI.parse(request.url).query
37 return(query ? CGI.parse(query) : {})
38 end
39
40 def post_parameters
41 post_body = {}
42
43 # Post params are only used if posting form data
44 if (request.headers['Content-Type'] && request.headers['Content-Type'].to_s.downcase.start_with?("application/x-www-form-urlencoded"))
45
46 request.post_body.split("&").each do |str|
47 param = str.split("=")
48 post_body[param[0]] = param[1]
49 end
50 end
51 post_body
52 end
53 end
54 end
+0
-66
vendor/oauth/request_proxy/em_http_request.rb less more
0 require 'oauth/request_proxy/base'
1 # em-http also uses adddressable so there is no need to require uri.
2 require 'em-http'
3 require 'cgi'
4
5 module OAuth::RequestProxy::EventMachine
6 class HttpRequest < OAuth::RequestProxy::Base
7
8 # A Proxy for use when you need to sign EventMachine::HttpClient instances.
9 # It needs to be called once the client is construct but before data is sent.
10 # Also see oauth/client/em-http
11 proxies ::EventMachine::HttpClient
12
13 # Request in this con
14
15 def method
16 request.method
17 end
18
19 def uri
20 request.normalize_uri.to_s
21 end
22
23 def parameters
24 if options[:clobber_request]
25 options[:parameters]
26 else
27 all_parameters
28 end
29 end
30
31 protected
32
33 def all_parameters
34 merged_parameters({}, post_parameters, query_parameters, options[:parameters])
35 end
36
37 def query_parameters
38 CGI.parse(request.normalize_uri.query.to_s)
39 end
40
41 def post_parameters
42 headers = request.options[:head] || {}
43 form_encoded = headers['Content-Type'].to_s.downcase.start_with?("application/x-www-form-urlencoded")
44 if ['POST', 'PUT'].include?(method) && form_encoded
45 CGI.parse(request.normalize_body.to_s)
46 else
47 {}
48 end
49 end
50
51 def merged_parameters(params, *extra_params)
52 extra_params.compact.each do |params_pairs|
53 params_pairs.each_pair do |key, value|
54 if params.has_key?(key)
55 params[key] += value
56 else
57 params[key] = [value].flatten
58 end
59 end
60 end
61 params
62 end
63
64 end
65 end
+0
-41
vendor/oauth/request_proxy/jabber_request.rb less more
0 require 'xmpp4r'
1 require 'oauth/request_proxy/base'
2
3 module OAuth
4 module RequestProxy
5 class JabberRequest < OAuth::RequestProxy::Base
6 proxies Jabber::Iq
7 proxies Jabber::Presence
8 proxies Jabber::Message
9
10 def parameters
11 return @params if @params
12
13 @params = {}
14
15 oauth = @request.get_elements('//oauth').first
16 return @params unless oauth
17
18 %w( oauth_token oauth_consumer_key oauth_signature_method oauth_signature
19 oauth_timestamp oauth_nonce oauth_version ).each do |param|
20 next unless element = oauth.first_element(param)
21 @params[param] = element.text
22 end
23
24 @params
25 end
26
27 def method
28 @request.name
29 end
30
31 def uri
32 [@request.from.strip.to_s, @request.to.strip.to_s].join("&")
33 end
34
35 def normalized_uri
36 uri
37 end
38 end
39 end
40 end
+0
-44
vendor/oauth/request_proxy/mock_request.rb less more
0 require 'oauth/request_proxy/base'
1
2 module OAuth
3 module RequestProxy
4 # RequestProxy for Hashes to facilitate simpler signature creation.
5 # Usage:
6 # request = OAuth::RequestProxy.proxy \
7 # "method" => "iq",
8 # "uri" => [from, to] * "&",
9 # "parameters" => {
10 # "oauth_consumer_key" => oauth_consumer_key,
11 # "oauth_token" => oauth_token,
12 # "oauth_signature_method" => "HMAC-SHA1"
13 # }
14 #
15 # signature = OAuth::Signature.sign \
16 # request,
17 # :consumer_secret => oauth_consumer_secret,
18 # :token_secret => oauth_token_secret,
19 class MockRequest < OAuth::RequestProxy::Base
20 proxies Hash
21
22 def parameters
23 @request["parameters"]
24 end
25
26 def method
27 @request["method"]
28 end
29
30 def normalized_uri
31 super
32 rescue
33 # if this is a non-standard URI, it may not parse properly
34 # in that case, assume that it's already been normalized
35 uri
36 end
37
38 def uri
39 @request["uri"]
40 end
41 end
42 end
43 end
+0
-73
vendor/oauth/request_proxy/net_http.rb less more
0 require 'oauth/request_proxy/base'
1 require 'net/http'
2 require 'uri'
3 require 'cgi'
4
5 module OAuth::RequestProxy::Net
6 module HTTP
7 class HTTPRequest < OAuth::RequestProxy::Base
8 proxies ::Net::HTTPGenericRequest
9
10 def method
11 request.method
12 end
13
14 def uri
15 options[:uri].to_s
16 end
17
18 def parameters
19 if options[:clobber_request]
20 options[:parameters]
21 else
22 all_parameters
23 end
24 end
25
26 def body
27 request.body
28 end
29
30 private
31
32 def all_parameters
33 request_params = CGI.parse(query_string)
34 # request_params.each{|k,v| request_params[k] = [nil] if v == []}
35
36 if options[:parameters]
37 options[:parameters].each do |k,v|
38 if request_params.has_key?(k) && v
39 request_params[k] << v
40 else
41 request_params[k] = [v]
42 end
43 end
44 end
45 request_params
46 end
47
48 def query_string
49 params = [ query_params, auth_header_params ]
50 params << post_params if (method.to_s.upcase == 'POST' || method.to_s.upcase == 'PUT') && form_url_encoded?
51 params.compact.join('&')
52 end
53
54 def form_url_encoded?
55 request['Content-Type'] != nil && request['Content-Type'].to_s.downcase.start_with?('application/x-www-form-urlencoded')
56 end
57
58 def query_params
59 URI.parse(request.path).query
60 end
61
62 def post_params
63 request.body
64 end
65
66 def auth_header_params
67 return nil unless request['Authorization'] && request['Authorization'][0,5] == 'OAuth'
68 auth_params = request['Authorization']
69 end
70 end
71 end
72 end
+0
-44
vendor/oauth/request_proxy/rack_request.rb less more
0 require 'oauth/request_proxy/base'
1 require 'uri'
2 require 'rack'
3
4 module OAuth::RequestProxy
5 class RackRequest < OAuth::RequestProxy::Base
6 proxies Rack::Request
7
8 def method
9 request.env["rack.methodoverride.original_method"] || request.request_method
10 end
11
12 def uri
13 request.url
14 end
15
16 def parameters
17 if options[:clobber_request]
18 options[:parameters] || {}
19 else
20 params = request_params.merge(query_params).merge(header_params)
21 params.merge(options[:parameters] || {})
22 end
23 end
24
25 def signature
26 parameters['oauth_signature']
27 end
28
29 protected
30
31 def query_params
32 request.GET
33 end
34
35 def request_params
36 if request.content_type and request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
37 request.POST
38 else
39 {}
40 end
41 end
42 end
43 end
+0
-53
vendor/oauth/request_proxy/typhoeus_request.rb less more
0 require 'oauth/request_proxy/base'
1 require 'typhoeus'
2 require 'typhoeus/request'
3 require 'uri'
4 require 'cgi'
5
6 module OAuth::RequestProxy::Typhoeus
7 class Request < OAuth::RequestProxy::Base
8 # Proxy for signing Typhoeus::Request requests
9 # Usage example:
10 # oauth_params = {:consumer => oauth_consumer, :token => access_token}
11 # req = Typhoeus::Request.new(uri, options)
12 # oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri))
13 # req.headers.merge!({"Authorization" => oauth_helper.header})
14 # hydra = Typhoeus::Hydra.new()
15 # hydra.queue(req)
16 # hydra.run
17 # response = req.response
18 proxies Typhoeus::Request
19
20 def method
21 request.method.to_s.upcase
22 end
23
24 def uri
25 options[:uri].to_s
26 end
27
28 def parameters
29 if options[:clobber_request]
30 options[:parameters]
31 else
32 post_parameters.merge(query_parameters).merge(options[:parameters] || {})
33 end
34 end
35
36 private
37
38 def query_parameters
39 query = URI.parse(request.url).query
40 query ? CGI.parse(query) : {}
41 end
42
43 def post_parameters
44 # Post params are only used if posting form data
45 if method == 'POST'
46 OAuth::Helper.stringify_keys(request.params || {})
47 else
48 {}
49 end
50 end
51 end
52 end
+0
-24
vendor/oauth/request_proxy.rb less more
0 module OAuth
1 module RequestProxy
2 def self.available_proxies #:nodoc:
3 @available_proxies ||= {}
4 end
5
6 def self.proxy(request, options = {})
7 return request if request.kind_of?(OAuth::RequestProxy::Base)
8
9 klass = available_proxies[request.class]
10
11 # Search for possible superclass matches.
12 if klass.nil?
13 request_parent = available_proxies.keys.find { |rc| request.kind_of?(rc) }
14 klass = available_proxies[request_parent]
15 end
16
17 raise UnknownRequestType, request.class.to_s unless klass
18 klass.new(request, options)
19 end
20
21 class UnknownRequestType < Exception; end
22 end
23 end
+0
-66
vendor/oauth/server.rb less more
0 require 'oauth/helper'
1 require 'oauth/consumer'
2
3 module OAuth
4 # This is mainly used to create consumer credentials and can pretty much be ignored if you want to create your own
5 class Server
6 include OAuth::Helper
7 attr_accessor :base_url
8
9 @@server_paths = {
10 :request_token_path => "/oauth/request_token",
11 :authorize_path => "/oauth/authorize",
12 :access_token_path => "/oauth/access_token"
13 }
14
15 # Create a new server instance
16 def initialize(base_url, paths = {})
17 @base_url = base_url
18 @paths = @@server_paths.merge(paths)
19 end
20
21 def generate_credentials
22 [generate_key(16), generate_key]
23 end
24
25 def generate_consumer_credentials(params = {})
26 Consumer.new(*generate_credentials)
27 end
28
29 # mainly for testing purposes
30 def create_consumer
31 creds = generate_credentials
32 Consumer.new(creds[0], creds[1],
33 {
34 :site => base_url,
35 :request_token_path => request_token_path,
36 :authorize_path => authorize_path,
37 :access_token_path => access_token_path
38 })
39 end
40
41 def request_token_path
42 @paths[:request_token_path]
43 end
44
45 def request_token_url
46 base_url + request_token_path
47 end
48
49 def authorize_path
50 @paths[:authorize_path]
51 end
52
53 def authorize_url
54 base_url + authorize_path
55 end
56
57 def access_token_path
58 @paths[:access_token_path]
59 end
60
61 def access_token_url
62 base_url + access_token_path
63 end
64 end
65 end
+0
-110
vendor/oauth/signature/base.rb less more
0 require 'oauth/signature'
1 require 'oauth/helper'
2 require 'oauth/request_proxy/base'
3 require 'base64'
4
5 module OAuth::Signature
6 class Base
7 include OAuth::Helper
8
9 attr_accessor :options
10 attr_reader :token_secret, :consumer_secret, :request
11
12 def self.implements(signature_method = nil)
13 return @implements if signature_method.nil?
14 @implements = signature_method
15 OAuth::Signature.available_methods[@implements] = self
16 end
17
18 def self.digest_class(digest_class = nil)
19 return @digest_class if digest_class.nil?
20 @digest_class = digest_class
21 end
22
23 def self.digest_klass(digest_klass = nil)
24 return @digest_klass if digest_klass.nil?
25 @digest_klass = digest_klass
26 end
27
28 def self.hash_class(hash_class = nil)
29 return @hash_class if hash_class.nil?
30 @hash_class = hash_class
31 end
32
33 def initialize(request, options = {}, &block)
34 raise TypeError unless request.kind_of?(OAuth::RequestProxy::Base)
35 @request = request
36 @options = options
37
38 ## consumer secret was determined beforehand
39
40 @consumer_secret = options[:consumer].secret if options[:consumer]
41
42 # presence of :consumer_secret option will override any Consumer that's provided
43 @consumer_secret = options[:consumer_secret] if options[:consumer_secret]
44
45 ## token secret was determined beforehand
46
47 @token_secret = options[:token].secret if options[:token]
48
49 # presence of :token_secret option will override any Token that's provided
50 @token_secret = options[:token_secret] if options[:token_secret]
51
52 # override secrets based on the values returned from the block (if any)
53 if block_given?
54 # consumer secret and token secret need to be looked up based on pieces of the request
55 secrets = yield block.arity == 1 ? request : [token, consumer_key, nonce, request.timestamp]
56 if secrets.is_a?(Array) && secrets.size == 2
57 @token_secret = secrets[0]
58 @consumer_secret = secrets[1]
59 end
60 end
61 end
62
63 def signature
64 Base64.encode64(digest).chomp.gsub(/\n/,'')
65 end
66
67 def ==(cmp_signature)
68 Base64.decode64(signature) == Base64.decode64(cmp_signature)
69 end
70
71 def verify
72 self == self.request.signature
73 end
74
75 def signature_base_string
76 request.signature_base_string
77 end
78
79 def body_hash
80 if self.class.hash_class
81 Base64.encode64(self.class.hash_class.digest(request.body || '')).chomp.gsub(/\n/,'')
82 else
83 nil # no body hash algorithm defined, so don't generate one
84 end
85 end
86
87 private
88
89 def token
90 request.token
91 end
92
93 def consumer_key
94 request.consumer_key
95 end
96
97 def nonce
98 request.nonce
99 end
100
101 def secret
102 "#{escape(consumer_secret)}&#{escape(token_secret)}"
103 end
104
105 def digest
106 self.class.digest_class.digest(signature_base_string)
107 end
108 end
109 end
+0
-15
vendor/oauth/signature/hmac/base.rb less more
0 # -*- encoding: utf-8 -*-
1
2 require 'oauth/signature/base'
3 require 'digest/hmac'
4
5 module OAuth::Signature::HMAC
6 class Base < OAuth::Signature::Base
7
8 private
9 def digest
10 self.class.digest_class Object.module_eval("::Digest::#{self.class.digest_klass}")
11 Digest::HMAC.digest(signature_base_string, secret, self.class.digest_class)
12 end
13 end
14 end
+0
-8
vendor/oauth/signature/hmac/md5.rb less more
0 require 'oauth/signature/hmac/base'
1
2 module OAuth::Signature::HMAC
3 class MD5 < Base
4 implements 'hmac-md5'
5 digest_class 'MD5'
6 end
7 end
+0
-8
vendor/oauth/signature/hmac/rmd160.rb less more
0 require 'oauth/signature/hmac/base'
1
2 module OAuth::Signature::HMAC
3 class RMD160 < Base
4 implements 'hmac-rmd160'
5 digest_klass 'RMD160'
6 end
7 end
+0
-9
vendor/oauth/signature/hmac/sha1.rb less more
0 require 'oauth/signature/hmac/base'
1
2 module OAuth::Signature::HMAC
3 class SHA1 < Base
4 implements 'hmac-sha1'
5 digest_klass 'SHA1'
6 hash_class ::Digest::SHA1
7 end
8 end
+0
-8
vendor/oauth/signature/hmac/sha2.rb less more
0 require 'oauth/signature/hmac/base'
1
2 module OAuth::Signature::HMAC
3 class SHA2 < Base
4 implements 'hmac-sha2'
5 digest_klass 'SHA2'
6 end
7 end
+0
-13
vendor/oauth/signature/md5.rb less more
0 require 'oauth/signature/base'
1 require 'digest/md5'
2
3 module OAuth::Signature
4 class MD5 < Base
5 implements 'md5'
6 digest_class Digest::MD5
7
8 def signature_base_string
9 secret + super
10 end
11 end
12 end
+0
-23
vendor/oauth/signature/plaintext.rb less more
0 require 'oauth/signature/base'
1
2 module OAuth::Signature
3 class PLAINTEXT < Base
4 implements 'plaintext'
5
6 def signature
7 signature_base_string
8 end
9
10 def ==(cmp_signature)
11 signature.to_s == cmp_signature.to_s
12 end
13
14 def signature_base_string
15 secret
16 end
17
18 def secret
19 super
20 end
21 end
22 end
+0
-46
vendor/oauth/signature/rsa/sha1.rb less more
0 require 'oauth/signature/base'
1 require 'openssl'
2
3 module OAuth::Signature::RSA
4 class SHA1 < OAuth::Signature::Base
5 implements 'rsa-sha1'
6 hash_class ::Digest::SHA1
7
8 def ==(cmp_signature)
9 public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(cmp_signature.is_a?(Array) ? cmp_signature.first : cmp_signature), signature_base_string)
10 end
11
12 def public_key
13 if consumer_secret.is_a?(String)
14 decode_public_key
15 elsif consumer_secret.is_a?(OpenSSL::X509::Certificate)
16 consumer_secret.public_key
17 else
18 consumer_secret
19 end
20 end
21
22 private
23
24 def decode_public_key
25 case consumer_secret
26 when /-----BEGIN CERTIFICATE-----/
27 OpenSSL::X509::Certificate.new( consumer_secret).public_key
28 else
29 OpenSSL::PKey::RSA.new( consumer_secret)
30 end
31 end
32
33 def digest
34 private_key = OpenSSL::PKey::RSA.new(
35 if options[:private_key_file]
36 IO.read(options[:private_key_file])
37 else
38 consumer_secret
39 end
40 )
41
42 private_key.sign(OpenSSL::Digest::SHA1.new, signature_base_string)
43 end
44 end
45 end
+0
-13
vendor/oauth/signature/sha1.rb less more
0 require 'oauth/signature/base'
1 require 'digest/sha1'
2
3 module OAuth::Signature
4 class SHA1 < Base
5 implements 'sha1'
6 digest_class Digest::SHA1
7
8 def signature_base_string
9 secret + super
10 end
11 end
12 end
+0
-45
vendor/oauth/signature.rb less more
0 module OAuth
1 module Signature
2 # Returns a list of available signature methods
3 def self.available_methods
4 @available_methods ||= {}
5 end
6
7 # Build a signature from a +request+.
8 #
9 # Raises UnknownSignatureMethod exception if the signature method is unknown.
10 def self.build(request, options = {}, &block)
11 request = OAuth::RequestProxy.proxy(request, options)
12 klass = available_methods[
13 (request.signature_method ||
14 ((c = request.options[:consumer]) && c.options[:signature_method]) ||
15 "").downcase]
16 raise UnknownSignatureMethod, request.signature_method unless klass
17 klass.new(request, options, &block)
18 end
19
20 # Sign a +request+
21 def self.sign(request, options = {}, &block)
22 self.build(request, options, &block).signature
23 end
24
25 # Verify the signature of +request+
26 def self.verify(request, options = {}, &block)
27 self.build(request, options, &block).verify
28 end
29
30 # Create the signature base string for +request+. This string is the normalized parameter information.
31 #
32 # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
33 def self.signature_base_string(request, options = {}, &block)
34 self.build(request, options, &block).signature_base_string
35 end
36
37 # Create the body hash for a request
38 def self.body_hash(request, options = {}, &block)
39 self.build(request, options, &block).body_hash
40 end
41
42 class UnknownSignatureMethod < Exception; end
43 end
44 end
+0
-7
vendor/oauth/token.rb less more
0 # this exists for backwards-compatibility
1
2 require 'oauth/tokens/token'
3 require 'oauth/tokens/server_token'
4 require 'oauth/tokens/consumer_token'
5 require 'oauth/tokens/request_token'
6 require 'oauth/tokens/access_token'
+0
-71
vendor/oauth/tokens/access_token.rb less more
0 module OAuth
1 # The Access Token is used for the actual "real" web service calls that you perform against the server
2 class AccessToken < ConsumerToken
3 # The less intrusive way. Otherwise, if we are to do it correctly inside consumer,
4 # we need to restructure and touch more methods: request(), sign!(), etc.
5 def request(http_method, path, *arguments)
6 request_uri = URI.parse(path)
7 site_uri = consumer.uri
8 is_service_uri_different = (request_uri.absolute? && request_uri != site_uri)
9 begin
10 consumer.uri(request_uri) if is_service_uri_different
11 @response = super(http_method, path, *arguments)
12 ensure
13 # NOTE: reset for wholesomeness? meaning that we admit only AccessToken service calls may use different URIs?
14 # so reset in case consumer is still used for other token-management tasks subsequently?
15 consumer.uri(site_uri) if is_service_uri_different
16 end
17 @response
18 end
19
20 # Make a regular GET request using AccessToken
21 #
22 # @response = @token.get('/people')
23 # @response = @token.get('/people', { 'Accept'=>'application/xml' })
24 #
25 def get(path, headers = {})
26 request(:get, path, headers)
27 end
28
29 # Make a regular HEAD request using AccessToken
30 #
31 # @response = @token.head('/people')
32 #
33 def head(path, headers = {})
34 request(:head, path, headers)
35 end
36
37 # Make a regular POST request using AccessToken
38 #
39 # @response = @token.post('/people')
40 # @response = @token.post('/people', { :name => 'Bob', :email => 'bob@mailinator.com' })
41 # @response = @token.post('/people', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' })
42 # @response = @token.post('/people', nil, {'Accept' => 'application/xml' })
43 # @response = @token.post('/people', @person.to_xml, { 'Accept'=>'application/xml', 'Content-Type' => 'application/xml' })
44 #
45 def post(path, body = '', headers = {})
46 request(:post, path, body, headers)
47 end
48
49 # Make a regular PUT request using AccessToken
50 #
51 # @response = @token.put('/people/123')
52 # @response = @token.put('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' })
53 # @response = @token.put('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' })
54 # @response = @token.put('/people/123', nil, { 'Accept' => 'application/xml' })
55 # @response = @token.put('/people/123', @person.to_xml, { 'Accept' => 'application/xml', 'Content-Type' => 'application/xml' })
56 #
57 def put(path, body = '', headers = {})
58 request(:put, path, body, headers)
59 end
60
61 # Make a regular DELETE request using AccessToken
62 #
63 # @response = @token.delete('/people/123')
64 # @response = @token.delete('/people/123', { 'Accept' => 'application/xml' })
65 #
66 def delete(path, headers = {})
67 request(:delete, path, headers)
68 end
69 end
70 end
+0
-33
vendor/oauth/tokens/consumer_token.rb less more
0 module OAuth
1 # Superclass for tokens used by OAuth Clients
2 class ConsumerToken < Token
3 attr_accessor :consumer, :params
4 attr_reader :response
5
6 def self.from_hash(consumer, hash)
7 token = self.new(consumer, hash[:oauth_token], hash[:oauth_token_secret])
8 token.params = hash
9 token
10 end
11
12 def initialize(consumer, token="", secret="")
13 super(token, secret)
14 @consumer = consumer
15 @params = {}
16 end
17
18 # Make a signed request using given http_method to the path
19 #
20 # @token.request(:get, '/people')
21 # @token.request(:post, '/people', @person.to_xml, { 'Content-Type' => 'application/xml' })
22 #
23 def request(http_method, path, *arguments)
24 @response = consumer.request(http_method, path, self, {}, *arguments)
25 end
26
27 # Sign a request generated elsewhere using Net:HTTP::Post.new or friends
28 def sign!(request, options = {})
29 consumer.sign!(request, self, options)
30 end
31 end
32 end
+0
-32
vendor/oauth/tokens/request_token.rb less more
0 module OAuth
1 # The RequestToken is used for the initial Request.
2 # This is normally created by the Consumer object.
3 class RequestToken < ConsumerToken
4
5 # Generate an authorization URL for user authorization
6 def authorize_url(params = nil)
7 params = (params || {}).merge(:oauth_token => self.token)
8 build_authorize_url(consumer.authorize_url, params)
9 end
10
11 def callback_confirmed?
12 params[:oauth_callback_confirmed] == "true"
13 end
14
15 # exchange for AccessToken on server
16 def get_access_token(options = {}, *arguments)
17 response = consumer.token_request(consumer.http_method, (consumer.access_token_url? ? consumer.access_token_url : consumer.access_token_path), self, options, *arguments)
18 OAuth::AccessToken.from_hash(consumer, response)
19 end
20
21 protected
22
23 # construct an authorization url
24 def build_authorize_url(base_url, params)
25 uri = URI.parse(base_url.to_s)
26 # TODO doesn't handle array values correctly
27 uri.query = params.map { |k,v| [k, CGI.escape(v)] * "=" } * "&"
28 uri.to_s
29 end
30 end
31 end
+0
-9
vendor/oauth/tokens/server_token.rb less more
0 module OAuth
1 # Used on the server for generating tokens
2 class ServerToken < Token
3
4 def initialize
5 super(generate_key(16), generate_key)
6 end
7 end
8 end
+0
-17
vendor/oauth/tokens/token.rb less more
0 module OAuth
1 # Superclass for the various tokens used by OAuth
2 class Token
3 include OAuth::Helper
4
5 attr_accessor :token, :secret
6
7 def initialize(token, secret)
8 @token = token
9 @secret = secret
10 end
11
12 def to_query
13 "oauth_token=#{escape(token)}&oauth_secret=#{escape(secret)}"
14 end
15 end
16 end
+0
-13
vendor/oauth.rb less more
0 $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
1
2 module OAuth
3 VERSION = "0.4.7"
4 end
5
6 require 'oauth/oauth'
7 require 'oauth/core_ext'
8
9 require 'oauth/client/helper'
10 require 'oauth/signature/hmac/sha1'
11 require 'oauth/signature/rsa/sha1'
12 require 'oauth/request_proxy/mock_request'
+0
-53
vendor/power_assert/enable_tracepoint_events.rb less more
0 if defined? RubyVM
1 basic_classes = [
2 Fixnum, Float, String, Array, Hash, Bignum, Symbol, Time, Regexp
3 ]
4
5 basic_operators = [
6 :+, :-, :*, :/, :%, :==, :===, :<, :<=, :<<, :[], :[]=,
7 :length, :size, :empty?, :succ, :>, :>=, :!, :!=, :=~, :freeze
8 ]
9
10 # set redefined flag
11 basic_classes.each do |klass|
12 basic_operators.each do |bop|
13 if klass.public_method_defined?(bop)
14 klass.instance_method(bop).owner.module_eval do
15 public bop
16 end
17 end
18 end
19 end
20
21 # bypass check_cfunc
22 verbose = $VERBOSE
23 begin
24 $VERBOSE = nil
25 module PowerAssert
26 refine BasicObject do
27 def !
28 end
29
30 def ==
31 end
32 end
33
34 refine Module do
35 def ==
36 end
37 end
38
39 refine Symbol do
40 def ==
41 end
42 end
43 end
44 ensure
45 $VERBOSE = verbose
46 end
47
48 # disable optimization
49 RubyVM::InstructionSequence.compile_option = {
50 specialized_instruction: false
51 }
52 end
+0
-3
vendor/power_assert/version.rb less more
0 module PowerAssert
1 VERSION = "0.2.3"
2 end
+0
-324
vendor/power_assert.rb less more
0 # power_assert.rb
1 #
2 # Copyright (C) 2014-2015 Kazuki Tsujimoto, All rights reserved.
3
4 begin
5 captured = false
6 TracePoint.new(:return, :c_return) do |tp|
7 captured = true
8 unless tp.binding and tp.return_value
9 raise
10 end
11 end.enable { __id__ }
12 raise unless captured
13 rescue
14 raise LoadError, 'Fully compatible TracePoint API required'
15 end
16
17 require 'power_assert/version'
18 require 'power_assert/enable_tracepoint_events'
19 require 'ripper'
20
21 module PowerAssert
22 class << self
23 def configuration
24 @configuration ||= Configuration[false]
25 end
26
27 def configure
28 yield configuration
29 end
30
31 def start(assertion_proc_or_source, assertion_method: nil, source_binding: TOPLEVEL_BINDING)
32 if respond_to?(:clear_global_method_cache, true)
33 clear_global_method_cache
34 end
35 yield Context.new(assertion_proc_or_source, assertion_method, source_binding)
36 end
37
38 private
39
40 if defined?(RubyVM)
41 def clear_global_method_cache
42 eval('using PowerAssert.const_get(:Empty)', TOPLEVEL_BINDING)
43 end
44 end
45 end
46
47 Configuration = Struct.new(:lazy_inspection)
48 private_constant :Configuration
49
50 module Empty
51 end
52 private_constant :Empty
53
54 class InspectedValue
55 def initialize(value)
56 @value = value
57 end
58
59 def inspect
60 @value
61 end
62 end
63 private_constant :InspectedValue
64
65 class SafeInspectable
66 def initialize(value)
67 @value = value
68 end
69
70 def inspect
71 inspected = @value.inspect
72 if Encoding.compatible?(Encoding.default_external, inspected)
73 inspected
74 else
75 begin
76 "#{inspected.encode(Encoding.default_external)}(#{inspected.encoding})"
77 rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
78 inspected.force_encoding(Encoding.default_external)
79 end
80 end
81 rescue => e
82 "InspectionFailure: #{e.class}: #{e.message.each_line.first}"
83 end
84 end
85 private_constant :SafeInspectable
86
87 class Context
88 Value = Struct.new(:name, :value, :column)
89 Ident = Struct.new(:type, :name, :column)
90
91 TARGET_CALLER_DIFF = {return: 5, c_return: 4}
92 TARGET_INDEX_OFFSET = {bmethod: 3, method: 2}
93
94 attr_reader :message_proc
95
96 def initialize(assertion_proc_or_source, assertion_method, source_binding)
97 if assertion_proc_or_source.kind_of?(Proc)
98 @assertion_proc = assertion_proc_or_source
99 @line = nil
100 else
101 @assertion_proc = source_binding.eval "Proc.new {#{assertion_proc_or_source}}"
102 @line = assertion_proc_or_source
103 end
104 path = nil
105 lineno = nil
106 methods = nil
107 refs = nil
108 method_ids = nil
109 return_values = []
110 @base_caller_length = -1
111 @assertion_method_name = assertion_method.to_s
112 @message_proc = -> {
113 return nil if @base_caller_length < 0
114 @message ||= build_assertion_message(@line || '', methods || [], return_values, refs || [], @assertion_proc.binding).freeze
115 }
116 @proc_local_variables = @assertion_proc.binding.eval('local_variables').map(&:to_s)
117 target_thread = Thread.current
118 @trace = TracePoint.new(:return, :c_return) do |tp|
119 next if method_ids and ! method_ids.include?(tp.method_id)
120 next unless tp.binding # workaround for ruby 2.2
121 locs = tp.binding.eval('::Kernel.caller_locations')
122 current_diff = locs.length - @base_caller_length
123 target_diff = TARGET_CALLER_DIFF[tp.event]
124 is_target_bmethod = current_diff < target_diff
125 if (is_target_bmethod or current_diff == target_diff) and Thread.current == target_thread
126 idx = target_diff - TARGET_INDEX_OFFSET[is_target_bmethod ? :bmethod : :method]
127 unless path
128 path = locs[idx].path
129 lineno = locs[idx].lineno
130 @line ||= open(path).each_line.drop(lineno - 1).first
131 idents = extract_idents(Ripper.sexp(@line))
132 methods, refs = idents.partition {|i| i.type == :method }
133 method_ids = methods.map(&:name).map(&:to_sym).uniq
134 end
135 if path == locs[idx].path and lineno == locs[idx].lineno
136 val = PowerAssert.configuration.lazy_inspection ?
137 tp.return_value :
138 InspectedValue.new(SafeInspectable.new(tp.return_value).inspect)
139 return_values << Value[tp.method_id.to_s, val, nil]
140 end
141 end
142 end
143 end
144
145 def yield
146 do_yield(&@assertion_proc)
147 end
148
149 def message
150 @message_proc.()
151 end
152
153 private
154
155 def do_yield
156 @trace.enable do
157 @base_caller_length = caller_locations.length
158 yield
159 end
160 end
161
162 def build_assertion_message(line, methods, return_values, refs, proc_binding)
163 set_column(methods, return_values)
164 ref_values = refs.map {|i| Value[i.name, proc_binding.eval(i.name), i.column] }
165 vals = (return_values + ref_values).find_all(&:column).sort_by(&:column).reverse
166 if vals.empty?
167 return line
168 end
169 fmt = (0..vals[0].column).map {|i| vals.find {|v| v.column == i } ? "%<#{i}>s" : ' ' }.join
170 ret = []
171 ret << line.chomp
172 ret << sprintf(fmt, vals.each_with_object({}) {|v, h| h[v.column.to_s.to_sym] = '|' }).chomp
173 vals.each do |i|
174 inspected_vals = vals.each_with_object({}) do |j, h|
175 h[j.column.to_s.to_sym] = [SafeInspectable.new(i.value).inspect, '|', ' '][i.column <=> j.column]
176 end
177 ret << encoding_safe_rstrip(sprintf(fmt, inspected_vals))
178 end
179 ret.join("\n")
180 end
181
182 def set_column(methods, return_values)
183 methods = methods.dup
184 return_values.each do |val|
185 idx = methods.index {|method| method.name == val.name }
186 if idx
187 val.column = methods.delete_at(idx).column
188 end
189 end
190 end
191
192 def encoding_safe_rstrip(str)
193 str.rstrip
194 rescue ArgumentError, Encoding::CompatibilityError
195 enc = str.encoding
196 if enc.ascii_compatible?
197 str.b.rstrip.force_encoding(enc)
198 else
199 str
200 end
201 end
202
203 def extract_idents(sexp)
204 tag, * = sexp
205 case tag
206 when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal
207 extract_idents(sexp[1])
208 when :assign, :massign
209 extract_idents(sexp[2])
210 when :assoclist_from_args, :bare_assoc_hash, :dyna_symbol, :paren, :string_embexpr,
211 :regexp_literal, :xstring_literal
212 sexp[1].flat_map {|s| extract_idents(s) }
213 when :assoc_new, :command, :dot2, :dot3, :string_content
214 sexp[1..-1].flat_map {|s| extract_idents(s) }
215 when :unary
216 handle_columnless_ident([], sexp[1], extract_idents(sexp[2]))
217 when :binary
218 handle_columnless_ident(extract_idents(sexp[1]), sexp[2], extract_idents(sexp[3]))
219 when :call
220 if sexp[3] == :call
221 handle_columnless_ident(extract_idents(sexp[1]), :call, [])
222 else
223 [sexp[1], sexp[3]].flat_map {|s| extract_idents(s) }
224 end
225 when :array
226 sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : []
227 when :command_call
228 [sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) }
229 when :aref
230 handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2]))
231 when :method_add_arg
232 idents = extract_idents(sexp[1])
233 if idents.empty?
234 # idents may be empty(e.g. ->{}.())
235 extract_idents(sexp[2])
236 else
237 idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]]
238 end
239 when :args_add_block
240 _, (tag, ss0, *ss1), _ = sexp
241 if tag == :args_add_star
242 (ss0 + ss1).flat_map {|s| extract_idents(s) }
243 else
244 sexp[1].flat_map {|s| extract_idents(s) }
245 end
246 when :vcall
247 _, (tag, name, (_, column)) = sexp
248 if tag == :@ident
249 [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]]
250 else
251 []
252 end
253 when :program
254 _, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp
255 if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and
256 (tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block)
257 ss.flat_map {|s| extract_idents(s) }
258 else
259 _, (s, *) = sexp
260 extract_idents(s)
261 end
262 when :var_ref
263 _, (tag, ref_name, (_, column)) = sexp
264 case tag
265 when :@kw
266 if ref_name == 'self'
267 [Ident[:ref, 'self', column]]
268 else
269 []
270 end
271 when :@const, :@cvar, :@ivar, :@gvar
272 [Ident[:ref, ref_name, column]]
273 else
274 []
275 end
276 when :@ident, :@const
277 _, method_name, (_, column) = sexp
278 [Ident[:method, method_name, column]]
279 else
280 []
281 end
282 end
283
284 def str_indices(str, re, offset, limit)
285 idx = str.index(re, offset)
286 if idx and idx <= limit
287 [idx, *str_indices(str, re, idx + 1, limit)]
288 else
289 []
290 end
291 end
292
293 MID2SRCTXT = {
294 :[] => '[',
295 :+@ => '+',
296 :-@ => '-',
297 :call => '('
298 }
299
300 def handle_columnless_ident(left_idents, mid, right_idents)
301 left_max = left_idents.max_by(&:column)
302 right_min = right_idents.min_by(&:column)
303 bg = left_max ? left_max.column + left_max.name.length : 0
304 ed = right_min ? right_min.column - 1 : @line.length - 1
305 mname = mid.to_s
306 srctxt = MID2SRCTXT[mid] || mname
307 re = /
308 #{'\b' if /\A\w/ =~ srctxt}
309 #{Regexp.escape(srctxt)}
310 #{'\b' if /\w\z/ =~ srctxt}
311 /x
312 indices = str_indices(@line, re, bg, ed)
313 if left_idents.empty? and right_idents.empty?
314 left_idents + right_idents
315 elsif left_idents.empty?
316 left_idents + right_idents + [Ident[:method, mname, indices.last]]
317 else
318 left_idents + right_idents + [Ident[:method, mname, indices.first]]
319 end
320 end
321 end
322 private_constant :Context
323 end
+0
-2
vendor/ruby_hmac.rb less more
0 # Convenience file to match gem name
1 require 'hmac'
+0
-25
vendor/test/unit/assertion-failed-error.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 module Test
7 module Unit
8
9 # Thrown by Test::Unit::Assertions when an assertion fails.
10 class AssertionFailedError < StandardError
11 attr_accessor :expected, :actual, :user_message
12 attr_accessor :inspected_expected, :inspected_actual
13 def initialize(message=nil, options=nil)
14 options ||= {}
15 @expected = options[:expected]
16 @actual = options[:actual]
17 @inspected_expected = options[:inspected_expected]
18 @inspected_actual = options[:inspected_actual]
19 @user_message = options[:user_message]
20 super(message)
21 end
22 end
23 end
24 end
+0
-2219
vendor/test/unit/assertions.rb less more
0 # Author:: Nathaniel Talbott.
1 # Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
2 # Copyright (c) 2009-2013 Kouhei Sutou. All rights reserved.
3 # License:: Ruby license.
4
5 require 'test/unit/assertion-failed-error'
6 require 'test/unit/util/backtracefilter'
7 require 'test/unit/util/method-owner-finder'
8 require 'test/unit/diff'
9
10 begin
11 require 'power_assert'
12 rescue LoadError, SyntaxError
13 end
14
15 module Test
16 module Unit
17
18 ##
19 # Test::Unit::Assertions contains the standard Test::Unit assertions.
20 # Assertions is included in Test::Unit::TestCase.
21 #
22 # To include it in your own code and use its functionality, you simply
23 # need to rescue Test::Unit::AssertionFailedError. Additionally you may
24 # override add_assertion to get notified whenever an assertion is made.
25 #
26 # Notes:
27 #
28 # * The message to each assertion, if given, will be propagated with the
29 # failure.
30 # * It is easy to add your own assertions based on assert_block().
31 #
32 # @example Example Custom Assertion
33 #
34 # def deny(boolean, message = nil)
35 # message = build_message message, '<?> is not false or nil.', boolean
36 # assert_block message do
37 # not boolean
38 # end
39 # end
40
41 module Assertions
42
43 ##
44 # The assertion upon which all other assertions are based. Passes if the
45 # block yields true.
46 #
47 # @example
48 # assert_block "Couldn't do the thing" do
49 # do_the_thing
50 # end
51 def assert_block(message="assert_block failed.")
52 _wrap_assertion do
53 if (! yield)
54 raise AssertionFailedError.new(message.to_s)
55 end
56 end
57 end
58
59 # @private
60 NOT_SPECIFIED = Object.new
61
62 # @overload assert(object, message=nil)
63 #
64 # Asserts that `object` is not false nor nil.
65 #
66 # Normally, you don't need to use this assertion. Use more
67 # specific assertions such as #assert_equal and
68 # #assert_include.
69 #
70 # @example Pass patterns
71 # assert(true) # => pass
72 # assert([1, 2].include?(1)) # => pass
73 #
74 # @example Failure patterns
75 # assert(nil) # => failure
76 # assert(false) # => failure
77 # assert([1, 2].include?(5)) # => failure
78 #
79 # @param [Object] object The check target.
80 # @param [String] message The additional user message. It is
81 # showed when the assertion is failed.
82 # @return [void]
83 #
84 # @overload assert(message=nil) {}
85 #
86 # Asserts that the givens block returns not false nor nil.
87 #
88 # This style uses Power Assert. It means that you can see each
89 # object values in method chains on failure. See the following
90 # example about Power Assert.
91 #
92 # @example Power Assert
93 # coins = [1, 5, 50]
94 # target_coin = 10
95 # assert do
96 # coins.include?(target_coin)
97 # end
98 # # =>
99 # # coins.include?(target_coin)
100 # # | | |
101 # # | | 10
102 # # | false
103 # # [1, 5, 50]
104 #
105 # We recommend you to use Power Assert for predicate method
106 # checks rather than existing assertions such as
107 # #assert_include and #assert_predicate. Power Assert shows
108 # useful message for debugging.
109 #
110 # We don't recommend you use Power Assert for equality
111 # check. You should use #assert_equal for the case. Because
112 # #assert_equal shows more useful message for debugging.
113 #
114 # @example Pass patterns
115 # assert {true} # => pass
116 # assert {[1, 2].include?(1)} # => pass
117 #
118 # @example Failure patterns
119 # assert {nil} # => failure
120 # assert {false} # => failure
121 # assert {[1, 2].include?(5)} # => failure
122 #
123 # @param [String] message The additional user message. It is
124 # showed when the assertion is failed.
125 # @yield [] Given no parameters to the block.
126 # @yieldreturn [Object] The checked object.
127 # @return [void]
128 def assert(object=NOT_SPECIFIED, message=nil, &block)
129 _wrap_assertion do
130 have_object = !NOT_SPECIFIED.equal?(object)
131 if block
132 message = object if have_object
133 if defined?(PowerAssert)
134 PowerAssert.start(block, :assertion_method => __callee__) do |pa|
135 pa_message = AssertionMessage.delayed_literal(&pa.message_proc)
136 assertion_message = build_message(message, "?", pa_message)
137 assert_block(assertion_message) do
138 pa.yield
139 end
140 end
141 else
142 assert(yield, message)
143 end
144 else
145 unless have_object
146 raise ArgumentError, "wrong number of arguments (0 for 1..2)"
147 end
148 assertion_message = nil
149 case message
150 when nil, String, Proc
151 when AssertionMessage
152 assertion_message = message
153 else
154 error_message = "assertion message must be String, Proc or "
155 error_message << "#{AssertionMessage}: "
156 error_message << "<#{message.inspect}>(<#{message.class}>)"
157 raise ArgumentError, error_message, filter_backtrace(caller)
158 end
159 assertion_message ||= build_message(message,
160 "<?> is not true.",
161 object)
162 assert_block(assertion_message) do
163 object
164 end
165 end
166 end
167 end
168
169 # Asserts that +object+ is false or nil.
170 #
171 # @note Just for minitest compatibility. :<
172 #
173 # @param [Object] object The object to be asserted.
174 # @return [void]
175 #
176 # @example Pass patterns
177 # refute(false) # => pass
178 # refute(nil) # => pass
179 #
180 # @example Failure patterns
181 # refute(true) # => failure
182 # refute("string") # => failure
183 #
184 # @since 2.5.3
185 def refute(object, message=nil)
186 _wrap_assertion do
187 assertion_message = nil
188 case message
189 when nil, String, Proc
190 when AssertionMessage
191 assertion_message = message
192 else
193 error_message = "assertion message must be String, Proc or "
194 error_message << "#{AssertionMessage}: "
195 error_message << "<#{message.inspect}>(<#{message.class}>)"
196 raise ArgumentError, error_message, filter_backtrace(caller)
197 end
198 assert_block("refute should not be called with a block.") do
199 !block_given?
200 end
201 assertion_message ||= build_message(message,
202 "<?> is neither nil or false.",
203 object)
204 assert_block(assertion_message) do
205 not object
206 end
207 end
208 end
209
210 ##
211 # Passes if +expected+ == +actual+.
212 #
213 # Note that the ordering of arguments is important, since a helpful
214 # error message is generated when this one fails that tells you the
215 # values of expected and actual.
216 #
217 # @example
218 # assert_equal 'MY STRING', 'my string'.upcase
219 def assert_equal(expected, actual, message=nil)
220 diff = AssertionMessage.delayed_diff(expected, actual)
221 if expected.respond_to?(:encoding) and
222 actual.respond_to?(:encoding) and
223 expected.encoding != actual.encoding
224 format = <<EOT
225 <?>(?) expected but was
226 <?>(?).?
227 EOT
228 full_message = build_message(message, format,
229 expected, expected.encoding.name,
230 actual, actual.encoding.name,
231 diff)
232 else
233 full_message = build_message(message, <<EOT, expected, actual, diff)
234 <?> expected but was
235 <?>.?
236 EOT
237 end
238 begin
239 assert_block(full_message) { expected == actual }
240 rescue AssertionFailedError => failure
241 _set_failed_information(failure, expected, actual, message)
242 raise failure # For JRuby. :<
243 end
244 end
245
246 ##
247 # Passes if the block raises one of the expected
248 # exceptions. When an expected exception is an Exception
249 # object, passes if expected_exception == actual_exception.
250 #
251 # @example
252 # assert_raise(RuntimeError, LoadError) do
253 # raise 'Boom!!!'
254 # end # -> pass
255 #
256 # assert_raise do
257 # raise Exception, 'Any exception should be raised!!!'
258 # end # -> pass
259 #
260 # assert_raise(RuntimeError.new("XXX")) {raise "XXX"} # -> pass
261 # assert_raise(MyError.new("XXX")) {raise "XXX"} # -> fail
262 # assert_raise(RuntimeError.new("ZZZ")) {raise "XXX"} # -> fail
263 def assert_raise(*args, &block)
264 assert_expected_exception = Proc.new do |*_args|
265 message, assert_exception_helper, actual_exception = _args
266 expected = assert_exception_helper.expected_exceptions
267 diff = AssertionMessage.delayed_diff(expected, actual_exception)
268 full_message = build_message(message,
269 "<?> exception expected but was\n<?>.?",
270 expected, actual_exception, diff)
271 begin
272 assert_block(full_message) do
273 expected == [] or
274 assert_exception_helper.expected?(actual_exception)
275 end
276 rescue AssertionFailedError => failure
277 _set_failed_information(failure, expected, actual_exception,
278 message)
279 raise failure # For JRuby. :<
280 end
281 end
282 _assert_raise(assert_expected_exception, *args, &block)
283 end
284
285 # Just for minitest compatibility. :<
286 alias_method :assert_raises, :assert_raise
287
288 ##
289 # Passes if the block raises one of the given
290 # exceptions or sub exceptions of the given exceptions.
291 #
292 # @example
293 # assert_raise_kind_of(SystemCallError) do
294 # raise Errno::EACCES
295 # end
296 def assert_raise_kind_of(*args, &block)
297 assert_expected_exception = Proc.new do |*_args|
298 message, assert_exception_helper, actual_exception = _args
299 expected = assert_exception_helper.expected_exceptions
300 full_message = build_message(message,
301 "<?> family exception expected " +
302 "but was\n<?>.",
303 expected, actual_exception)
304 assert_block(full_message) do
305 assert_exception_helper.expected?(actual_exception, :kind_of?)
306 end
307 end
308 _assert_raise(assert_expected_exception, *args, &block)
309 end
310
311
312 ##
313 # Passes if +object+.instance_of?(+klass+). When +klass+ is
314 # an array of classes, it passes if any class
315 # satisfies +object.instance_of?(class).
316 #
317 # @example
318 # assert_instance_of(String, 'foo') # -> pass
319 # assert_instance_of([Fixnum, NilClass], 100) # -> pass
320 # assert_instance_of([Numeric, NilClass], 100) # -> fail
321 def assert_instance_of(klass, object, message="")
322 _wrap_assertion do
323 if klass.is_a?(Array)
324 klasses = klass
325 else
326 klasses = [klass]
327 end
328 assert_block("The first parameter to assert_instance_of should be " +
329 "a Class or an Array of Class.") do
330 klasses.all? {|k| k.is_a?(Class)}
331 end
332 klass_message = AssertionMessage.maybe_container(klass) do |value|
333 "<#{value}>"
334 end
335 full_message = build_message(message, <<EOT, object, klass_message, object.class)
336 <?> expected to be instance_of\\?
337 ? but was
338 <?>.
339 EOT
340 assert_block(full_message) do
341 klasses.any? {|k| object.instance_of?(k)}
342 end
343 end
344 end
345
346 ##
347 # Passes if +object+.instance_of?(+klass+) does not hold.
348 # When +klass+ is an array of classes, it passes if no class
349 # satisfies +object.instance_of?(class).
350 #
351 # @example
352 # assert_not_instance_of(String, 100) # -> pass
353 # assert_not_instance_of([Fixnum, NilClass], '100') # -> pass
354 # assert_not_instance_of([Numeric, NilClass], 100) # -> fail
355 #
356 # @since 3.0.0
357 def assert_not_instance_of(klass, object, message="")
358 _wrap_assertion do
359 if klass.is_a?(Array)
360 klasses = klass
361 else
362 klasses = [klass]
363 end
364 assert_block("The first parameter to assert_not_instance_of should be " <<
365 "a Class or an Array of Class.") do
366 klasses.all? {|k| k.is_a?(Class)}
367 end
368 klass_message = AssertionMessage.maybe_container(klass) do |value|
369 "<#{value}>"
370 end
371 full_message = build_message(message,
372 "<?> expected to not be instance_of\\?\n" +
373 "? but was.",
374 object,
375 klass_message)
376 assert_block(full_message) do
377 klasses.none? {|k| object.instance_of?(k)}
378 end
379 end
380 end
381
382 # Just for minitest compatibility. :<
383 #
384 # @since 3.0.0
385 alias_method :refute_instance_of, :assert_not_instance_of
386
387 ##
388 # Passes if +object+ is nil.
389 #
390 # @example
391 # assert_nil [1, 2].uniq!
392 def assert_nil(object, message="")
393 full_message = build_message(message, <<EOT, object)
394 <?> expected to be nil.
395 EOT
396 assert_block(full_message) { object.nil? }
397 end
398
399 ##
400 # Passes if +object+.kind_of?(+klass+). When +klass+ is
401 # an array of classes or modules, it passes if any
402 # class or module satisfies +object.kind_of?(class_or_module).
403 #
404 # @example
405 # assert_kind_of(Object, 'foo') # -> pass
406 # assert_kind_of([Fixnum, NilClass], 100) # -> pass
407 # assert_kind_of([Fixnum, NilClass], "string") # -> fail
408 def assert_kind_of(klass, object, message="")
409 _wrap_assertion do
410 if klass.is_a?(Array)
411 klasses = klass
412 else
413 klasses = [klass]
414 end
415 assert_block("The first parameter to assert_kind_of should be " +
416 "a kind_of Module or an Array of a kind_of Module.") do
417 klasses.all? {|k| k.kind_of?(Module)}
418 end
419 klass_message = AssertionMessage.maybe_container(klass) do |value|
420 "<#{value}>"
421 end
422 full_message = build_message(message,
423 "<?> expected to be kind_of\\?\n" +
424 "? but was\n" +
425 "<?>.",
426 object,
427 klass_message,
428 object.class)
429 assert_block(full_message) do
430 klasses.any? {|k| object.kind_of?(k)}
431 end
432 end
433 end
434
435 ##
436 # Passes if +object+.kind_of?(+klass+) does not hold.
437 # When +klass+ is an array of classes or modules, it passes only if all
438 # classes (and modules) do not satisfy +object.kind_of?(class_or_module).
439 #
440 # @example
441 # assert_not_kind_of(Fixnum, 'foo') # -> pass
442 # assert_not_kind_of([Fixnum, NilClass], '0') # -> pass
443 # assert_not_kind_of([Fixnum, NilClass], 100) # -> fail
444 #
445 # @since 3.0.0
446 def assert_not_kind_of(klass, object, message="")
447 _wrap_assertion do
448 if klass.is_a?(Array)
449 klasses = klass
450 else
451 klasses = [klass]
452 end
453 assert_block("The first parameter to assert_not_kind_of should be " +
454 "a kind_of Module or an Array of a kind_of Module.") do
455 klasses.all? {|k| k.kind_of?(Module)}
456 end
457 klass_message = AssertionMessage.maybe_container(klass) do |value|
458 "<#{value}>"
459 end
460 full_message = build_message(message,
461 "<?> expected to not be kind_of\\?\n" +
462 "? but was.",
463 object,
464 klass_message)
465 assert_block(full_message) do
466 klasses.none? {|k| object.kind_of?(k)}
467 end
468 end
469 end
470
471 # Just for minitest compatibility. :<
472 #
473 # @since 3.0.0
474 alias_method :refute_kind_of, :assert_not_kind_of
475
476 ##
477 # Passes if +object+ .respond_to? +method+
478 #
479 # @example
480 # assert_respond_to 'bugbear', :slice
481 def assert_respond_to(object, method, message="")
482 _wrap_assertion do
483 full_message = build_message(message,
484 "<?>.kind_of\\?(Symbol) or\n" +
485 "<?>.respond_to\\?(:to_str) expected",
486 method, method)
487 assert_block(full_message) do
488 method.kind_of?(Symbol) or method.respond_to?(:to_str)
489 end
490 full_message = build_message(message,
491 "<?>.respond_to\\?(?) expected\n" +
492 "(Class: <?>)",
493 object, method, object.class)
494 assert_block(full_message) {object.respond_to?(method)}
495 end
496 end
497
498 ##
499 # Passes if +object+ does not .respond_to? +method+.
500 #
501 # @example
502 # assert_not_respond_to('bugbear', :nonexistence) # -> pass
503 # assert_not_respond_to('bugbear', :size) # -> fail
504 def assert_not_respond_to(object, method, message="")
505 _wrap_assertion do
506 full_message = build_message(message,
507 "<?>.kind_of\\?(Symbol) or\n" +
508 "<?>.respond_to\\?(:to_str) expected",
509 method, method)
510 assert_block(full_message) do
511 method.kind_of?(Symbol) or method.respond_to?(:to_str)
512 end
513 full_message = build_message(message,
514 "!<?>.respond_to\\?(?) expected\n" +
515 "(Class: <?>)",
516 object, method, object.class)
517 assert_block(full_message) {!object.respond_to?(method)}
518 end
519 end
520
521 # Just for minitest compatibility. :<
522 #
523 # @since 2.5.3
524 alias_method :refute_respond_to, :assert_not_respond_to
525
526 ##
527 # Passes if +pattern+ =~ +string+.
528 #
529 # @example
530 # assert_match(/\d+/, 'five, 6, seven')
531 def assert_match(pattern, string, message="")
532 _wrap_assertion do
533 pattern = case(pattern)
534 when String
535 Regexp.new(Regexp.escape(pattern))
536 else
537 pattern
538 end
539 full_message = build_message(message, "<?> expected to be =~\n<?>.",
540 pattern, string)
541 assert_block(full_message) { pattern =~ string }
542 end
543 end
544
545 ##
546 # Passes if +actual+ .equal? +expected+ (i.e. they are the same
547 # instance).
548 #
549 # @example
550 # o = Object.new
551 # assert_same o, o
552 def assert_same(expected, actual, message="")
553 full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
554 <?>
555 with id <?> expected to be equal\\? to
556 <?>
557 with id <?>.
558 EOT
559 assert_block(full_message) { actual.equal?(expected) }
560 end
561
562 ##
563 # Compares the +object1+ with +object2+ using +operator+.
564 #
565 # Passes if object1.__send__(operator, object2) is true.
566 #
567 # @example
568 # assert_operator 5, :>=, 4
569 def assert_operator(object1, operator, object2, message="")
570 _wrap_assertion do
571 full_message = build_message(nil, "<?>\ngiven as the operator for #assert_operator must be a Symbol or #respond_to\\?(:to_str).", operator)
572 assert_block(full_message){operator.kind_of?(Symbol) || operator.respond_to?(:to_str)}
573 full_message = build_message(message, <<EOT, object1, AssertionMessage.literal(operator), object2)
574 <?> expected to be
575 ?
576 <?>.
577 EOT
578 assert_block(full_message) { object1.__send__(operator, object2) }
579 end
580 end
581
582 ##
583 # Compares the +object1+ with +object2+ using +operator+.
584 #
585 # Passes if object1.__send__(operator, object2) is not true.
586 #
587 # @example
588 # assert_not_operator(5, :<, 4) # => pass
589 # assert_not_operator(5, :>, 4) # => fail
590 #
591 # @since 3.0.0
592 def assert_not_operator(object1, operator, object2, message="")
593 _wrap_assertion do
594 full_message = build_message(nil, "<?>\ngiven as the operator for #assert_not_operator must be a Symbol or #respond_to\\?(:to_str).", operator)
595 assert_block(full_message){operator.kind_of?(Symbol) || operator.respond_to?(:to_str)}
596 full_message = build_message(message, <<EOT, object1, AssertionMessage.literal(operator), object2)
597 <?> expected to not be
598 ?
599 <?>.
600 EOT
601 assert_block(full_message) { ! object1.__send__(operator, object2) }
602 end
603 end
604
605 # Just for minitest compatibility. :<
606 #
607 # @since 3.0.0
608 alias_method :refute_operator, :assert_not_operator
609
610 ##
611 # Passes if block does not raise an exception.
612 #
613 # @example
614 # assert_nothing_raised do
615 # [1, 2].uniq
616 # end
617 def assert_nothing_raised(*args)
618 _wrap_assertion do
619 if args.last.is_a?(String)
620 message = args.pop
621 else
622 message = ""
623 end
624
625 assert_exception_helper = AssertExceptionHelper.new(self, args)
626 begin
627 yield
628 rescue Exception => e
629 if ((args.empty? && !e.instance_of?(AssertionFailedError)) ||
630 assert_exception_helper.expected?(e))
631 failure_message = build_message(message, "Exception raised:\n?", e)
632 assert_block(failure_message) {false}
633 else
634 raise
635 end
636 end
637 nil
638 end
639 end
640
641 ##
642 # Flunk always fails.
643 #
644 # @example
645 # flunk 'Not done testing yet.'
646 def flunk(message="Flunked")
647 assert_block(build_message(message)){false}
648 end
649
650 ##
651 # Passes if ! +actual+ .equal? +expected+
652 #
653 # @example
654 # assert_not_same Object.new, Object.new
655 def assert_not_same(expected, actual, message="")
656 full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
657 <?>
658 with id <?> expected to not be equal\\? to
659 <?>
660 with id <?>.
661 EOT
662 assert_block(full_message) { !actual.equal?(expected) }
663 end
664
665 # Just for minitest compatibility. :<
666 #
667 # @since 2.5.3
668 alias_method :refute_same, :assert_not_same
669
670 ##
671 # Passes if +expected+ != +actual+
672 #
673 # @example
674 # assert_not_equal 'some string', 5
675 def assert_not_equal(expected, actual, message="")
676 full_message = build_message(message, "<?> expected to be != to\n<?>.", expected, actual)
677 assert_block(full_message) { expected != actual }
678 end
679
680 # Just for minitest compatibility. :<
681 #
682 # @since 2.5.3
683 alias_method :refute_equal, :assert_not_equal
684
685 ##
686 # Passes if ! +object+ .nil?
687 #
688 # @example
689 # assert_not_nil '1 two 3'.sub!(/two/, '2')
690 def assert_not_nil(object, message="")
691 full_message = build_message(message, "<?> expected to not be nil.", object)
692 assert_block(full_message){!object.nil?}
693 end
694
695 # Just for minitest compatibility. :<
696 #
697 # @since 2.5.3
698 alias_method :refute_nil, :assert_not_nil
699
700 ##
701 # Passes if +regexp+ !~ +string+
702 #
703 # @example
704 # assert_not_match(/two/, 'one 2 three') # -> pass
705 # assert_not_match(/three/, 'one 2 three') # -> fail
706 def assert_not_match(regexp, string, message="")
707 _wrap_assertion do
708 assert_instance_of(Regexp, regexp,
709 "<REGEXP> in assert_not_match(<REGEXP>, ...) " +
710 "should be a Regexp.")
711 full_message = build_message(message,
712 "<?> expected to not match\n<?>.",
713 regexp, string)
714 assert_block(full_message) { regexp !~ string }
715 end
716 end
717
718 # Just for minitest compatibility. :<
719 #
720 # @since 2.5.3
721 alias_method :refute_match, :assert_not_match
722
723 ##
724 # Deprecated. Use #assert_not_match instead.
725 #
726 # Passes if +regexp+ !~ +string+
727 #
728 # @example
729 # assert_no_match(/two/, 'one 2 three') # -> pass
730 # assert_no_match(/three/, 'one 2 three') # -> fail
731 def assert_no_match(regexp, string, message="")
732 _wrap_assertion do
733 assert_instance_of(Regexp, regexp,
734 "The first argument to assert_no_match " +
735 "should be a Regexp.")
736 assert_not_match(regexp, string, message)
737 end
738 end
739
740 # @private
741 class ThrowTagExtractor
742 @@have_uncaught_throw_error = const_defined?(:UncaughtThrowError)
743
744 UncaughtThrowPatterns = {
745 NameError => /^uncaught throw `(.+)'$/,
746 ArgumentError => /^uncaught throw (`.+'|.+)$/,
747 ThreadError => /^uncaught throw `(.+)' in thread /,
748 }
749
750 def initialize(error)
751 @error = error
752 end
753
754 def extract_tag
755 tag = nil
756 if @@have_uncaught_throw_error
757 return nil unless @error.is_a?(UncaughtThrowError)
758 tag = @error.tag
759 else
760 pattern = UncaughtThrowPatterns[@error.class]
761 return nil if pattern.nil?
762 return nil unless pattern =~ @error.message
763 tag = $1
764 end
765 normalize_tag(tag)
766 end
767
768 private
769 def normalize_tag(tag)
770 case tag
771 when /\A:/
772 tag[1..-1].intern
773 when /\A`(.+)'\z/
774 $1.intern
775 when String
776 tag.intern
777 else
778 tag
779 end
780 end
781 end
782
783 ##
784 # Passes if the block throws +expected_object+
785 #
786 # @example
787 # assert_throw(:done) do
788 # throw(:done)
789 # end
790 def assert_throw(expected_object, message="", &proc)
791 _wrap_assertion do
792 begin
793 catch([]) {}
794 rescue TypeError
795 assert_instance_of(Symbol, expected_object,
796 "assert_throws expects the symbol that should be thrown for its first argument")
797 end
798 assert_block("Should have passed a block to assert_throw.") do
799 block_given?
800 end
801 caught = true
802 begin
803 catch(expected_object) do
804 proc.call
805 caught = false
806 end
807 full_message = build_message(message,
808 "<?> should have been thrown.",
809 expected_object)
810 assert_block(full_message) {caught}
811 rescue => error
812 extractor = ThrowTagExtractor.new(error)
813 tag = extractor.extract_tag
814 raise if tag.nil?
815 full_message = build_message(message,
816 "<?> expected to be thrown but\n" +
817 "<?> was thrown.",
818 expected_object, tag)
819 flunk(full_message)
820 end
821 end
822 end
823
824 # Just for minitest compatibility. :<
825 #
826 # @since 2.5.3
827 alias_method :assert_throws, :assert_throw
828
829 ##
830 # Passes if block does not throw anything.
831 #
832 # @example
833 # assert_nothing_thrown do
834 # [1, 2].uniq
835 # end
836 def assert_nothing_thrown(message="", &proc)
837 _wrap_assertion do
838 assert(block_given?, "Should have passed a block to assert_nothing_thrown")
839 begin
840 proc.call
841 rescue => error
842 extractor = ThrowTagExtractor.new(error)
843 tag = extractor.extract_tag
844 raise if tag.nil?
845 full_message = build_message(message,
846 "<?> was thrown when nothing was expected",
847 tag)
848 flunk(full_message)
849 end
850 assert(true, "Expected nothing to be thrown")
851 end
852 end
853
854 ##
855 # Passes if +expected_float+ and +actual_float+ are equal
856 # within +delta+ tolerance.
857 #
858 # @example
859 # assert_in_delta 0.05, (50000.0 / 10**6), 0.00001
860 def assert_in_delta(expected_float, actual_float, delta=0.001, message="")
861 _wrap_assertion do
862 _assert_in_delta_validate_arguments(expected_float,
863 actual_float,
864 delta)
865 full_message = _assert_in_delta_message(expected_float,
866 actual_float,
867 delta,
868 message)
869 assert_block(full_message) do
870 (expected_float.to_f - actual_float.to_f).abs <= delta.to_f
871 end
872 end
873 end
874
875 ##
876 # Passes if +expected_float+ and +actual_float+ are
877 # not equal within +delta+ tolerance.
878 #
879 # @example
880 # assert_not_in_delta(0.05, (50000.0 / 10**6), 0.00002) # -> pass
881 # assert_not_in_delta(0.05, (50000.0 / 10**6), 0.00001) # -> fail
882 def assert_not_in_delta(expected_float, actual_float, delta=0.001, message="")
883 _wrap_assertion do
884 _assert_in_delta_validate_arguments(expected_float,
885 actual_float,
886 delta)
887 full_message = _assert_in_delta_message(expected_float,
888 actual_float,
889 delta,
890 message,
891 :negative_assertion => true)
892 assert_block(full_message) do
893 (expected_float.to_f - actual_float.to_f).abs > delta.to_f
894 end
895 end
896 end
897
898 # Just for minitest compatibility. :<
899 #
900 # @since 2.5.3
901 alias_method :refute_in_delta, :assert_not_in_delta
902
903 private
904 def _assert_in_delta_validate_arguments(expected_float,
905 actual_float,
906 delta)
907 {
908 expected_float => "first float",
909 actual_float => "second float",
910 delta => "delta"
911 }.each do |float, name|
912 assert_respond_to(float, :to_f,
913 "The arguments must respond to to_f; " +
914 "the #{name} did not")
915 end
916 delta = delta.to_f
917 assert_operator(delta, :>=, 0.0, "The delta should not be negative")
918 end
919
920 def _assert_in_delta_message(expected_float, actual_float, delta,
921 message, options={})
922 if options[:negative_assertion]
923 format = <<-EOT
924 <?> -/+ <?> expected to not include
925 <?>.
926 EOT
927 else
928 format = <<-EOT
929 <?> -/+ <?> expected to include
930 <?>.
931 EOT
932 end
933 arguments = [expected_float, delta, actual_float]
934 normalized_expected = expected_float.to_f
935 normalized_actual = actual_float.to_f
936 normalized_delta = delta.to_f
937 relation_format = nil
938 relation_arguments = nil
939 if normalized_actual < normalized_expected - normalized_delta
940 relation_format = "<<?> < <?>-<?>[?] <= <?>+<?>[?]>"
941 relation_arguments = [actual_float,
942 expected_float, delta,
943 normalized_expected - normalized_delta,
944 expected_float, delta,
945 normalized_expected + normalized_delta]
946 elsif normalized_actual <= normalized_expected + normalized_delta
947 relation_format = "<<?>-<?>[?] <= <?> <= <?>+<?>[?]>"
948 relation_arguments = [expected_float, delta,
949 normalized_expected - normalized_delta,
950 actual_float,
951 expected_float, delta,
952 normalized_expected + normalized_delta]
953 else
954 relation_format = "<<?>-<?>[?] <= <?>+<?>[?] < <?>>"
955 relation_arguments = [expected_float, delta,
956 normalized_expected - normalized_delta,
957 expected_float, delta,
958 normalized_expected + normalized_delta,
959 actual_float]
960 end
961
962 if relation_format
963 format << <<-EOT
964
965 Relation:
966 #{relation_format}
967 EOT
968 arguments.concat(relation_arguments)
969 end
970
971 build_message(message, format, *arguments)
972 end
973
974 public
975 ##
976 # Passes if +expected_float+ and +actual_float+ are equal
977 # within +epsilon+ relative error of +expected_float+.
978 #
979 # @example
980 # assert_in_epsilon(10000.0, 9900.0, 0.1) # -> pass
981 # assert_in_epsilon(10000.0, 9899.0, 0.1) # -> fail
982 def assert_in_epsilon(expected_float, actual_float, epsilon=0.001,
983 message="")
984 _wrap_assertion do
985 _assert_in_epsilon_validate_arguments(expected_float,
986 actual_float,
987 epsilon)
988 full_message = _assert_in_epsilon_message(expected_float,
989 actual_float,
990 epsilon,
991 message)
992 assert_block(full_message) do
993 normalized_expected_float = expected_float.to_f
994 if normalized_expected_float.zero?
995 delta = epsilon.to_f ** 2
996 else
997 delta = normalized_expected_float * epsilon.to_f
998 end
999 delta = delta.abs
1000 (normalized_expected_float - actual_float.to_f).abs <= delta
1001 end
1002 end
1003 end
1004
1005 ##
1006 # Passes if +expected_float+ and +actual_float+ are
1007 # not equal within +epsilon+ relative error of
1008 # +expected_float+.
1009 #
1010 # @example
1011 # assert_not_in_epsilon(10000.0, 9900.0, 0.1) # -> fail
1012 # assert_not_in_epsilon(10000.0, 9899.0, 0.1) # -> pass
1013 def assert_not_in_epsilon(expected_float, actual_float, epsilon=0.001,
1014 message="")
1015 _wrap_assertion do
1016 _assert_in_epsilon_validate_arguments(expected_float,
1017 actual_float,
1018 epsilon)
1019 full_message = _assert_in_epsilon_message(expected_float,
1020 actual_float,
1021 epsilon,
1022 message,
1023 :negative_assertion => true)
1024 assert_block(full_message) do
1025 normalized_expected_float = expected_float.to_f
1026 delta = normalized_expected_float * epsilon.to_f
1027 (normalized_expected_float - actual_float.to_f).abs > delta
1028 end
1029 end
1030 end
1031
1032 # Just for minitest compatibility. :<
1033 #
1034 # @since 3.0.0
1035 alias_method :refute_in_epsilon, :assert_not_in_epsilon
1036
1037 private
1038 def _assert_in_epsilon_validate_arguments(expected_float,
1039 actual_float,
1040 epsilon)
1041 {
1042 expected_float => "first float",
1043 actual_float => "second float",
1044 epsilon => "epsilon"
1045 }.each do |float, name|
1046 assert_respond_to(float, :to_f,
1047 "The arguments must respond to to_f; " +
1048 "the #{name} did not")
1049 end
1050 epsilon = epsilon.to_f
1051 assert_operator(epsilon, :>=, 0.0, "The epsilon should not be negative")
1052 end
1053
1054 def _assert_in_epsilon_message(expected_float, actual_float, epsilon,
1055 message, options={})
1056 normalized_expected = expected_float.to_f
1057 normalized_actual = actual_float.to_f
1058 normalized_epsilon = epsilon.to_f
1059 delta = normalized_expected * normalized_epsilon
1060
1061 if options[:negative_assertion]
1062 format = <<-EOT
1063 <?> -/+ (<?> * <?>)[?] expected to not include
1064 <?>.
1065 EOT
1066 else
1067 format = <<-EOT
1068 <?> -/+ (<?> * <?>)[?] expected to include
1069 <?>.
1070 EOT
1071 end
1072 arguments = [expected_float, expected_float, epsilon, delta,
1073 actual_float]
1074
1075 relation_format = nil
1076 relation_arguments = nil
1077 if normalized_actual < normalized_expected - delta
1078 relation_format = "<<?> < <?>-(<?>*<?>)[?] <= <?>+(<?>*<?>)[?]>"
1079 relation_arguments = [actual_float,
1080 expected_float, expected_float, epsilon,
1081 normalized_expected - delta,
1082 expected_float, expected_float, epsilon,
1083 normalized_expected + delta]
1084 elsif normalized_actual <= normalized_expected + delta
1085 relation_format = "<<?>-(<?>*<?>)[?] <= <?> <= <?>+(<?>*<?>)[?]>"
1086 relation_arguments = [expected_float, expected_float, epsilon,
1087 normalized_expected - delta,
1088 actual_float,
1089 expected_float, expected_float, epsilon,
1090 normalized_expected + delta]
1091 else
1092 relation_format = "<<?>-(<?>*<?>)[?] <= <?>+(<?>*<?>)[?] < <?>>"
1093 relation_arguments = [expected_float, expected_float, epsilon,
1094 normalized_expected - delta,
1095 expected_float, expected_float, epsilon,
1096 normalized_expected + delta,
1097 actual_float]
1098 end
1099
1100 if relation_format
1101 format << <<-EOT
1102
1103 Relation:
1104 #{relation_format}
1105 EOT
1106 arguments.concat(relation_arguments)
1107 end
1108
1109 build_message(message, format, *arguments)
1110 end
1111
1112 public
1113 ##
1114 # Passes if the method send returns a true value.
1115 #
1116 # +send_array+ is composed of:
1117 # * A receiver
1118 # * A method
1119 # * Arguments to the method
1120 #
1121 # @example
1122 # assert_send([[1, 2], :member?, 1]) # -> pass
1123 # assert_send([[1, 2], :member?, 4]) # -> fail
1124 def assert_send(send_array, message=nil)
1125 _wrap_assertion do
1126 assert_instance_of(Array, send_array,
1127 "assert_send requires an array " +
1128 "of send information")
1129 assert_operator(send_array.size, :>=, 2,
1130 "assert_send requires at least a receiver " +
1131 "and a message name")
1132 format = <<EOT
1133 <?> expected to respond to
1134 <?(*?)> with a true value but was
1135 <?>.
1136 EOT
1137 receiver, message_name, *arguments = send_array
1138 result = nil
1139 full_message =
1140 build_message(message,
1141 format,
1142 receiver,
1143 AssertionMessage.literal(message_name.to_s),
1144 arguments,
1145 AssertionMessage.delayed_literal {result})
1146 assert_block(full_message) do
1147 result = receiver.__send__(message_name, *arguments)
1148 result
1149 end
1150 end
1151 end
1152
1153 ##
1154 # Passes if the method send doesn't return a true value.
1155 #
1156 # +send_array+ is composed of:
1157 # * A receiver
1158 # * A method
1159 # * Arguments to the method
1160 #
1161 # @example
1162 # assert_not_send([[1, 2], :member?, 1]) # -> fail
1163 # assert_not_send([[1, 2], :member?, 4]) # -> pass
1164 def assert_not_send(send_array, message=nil)
1165 _wrap_assertion do
1166 assert_instance_of(Array, send_array,
1167 "assert_not_send requires an array " +
1168 "of send information")
1169 assert_operator(send_array.size, :>=, 2,
1170 "assert_not_send requires at least a receiver " +
1171 "and a message name")
1172 format = <<EOT
1173 <?> expected to respond to
1174 <?(*?)> with not a true value but was
1175 <?>.
1176 EOT
1177 receiver, message_name, *arguments = send_array
1178 result = nil
1179 full_message =
1180 build_message(message,
1181 format,
1182 receiver,
1183 AssertionMessage.literal(message_name.to_s),
1184 arguments,
1185 AssertionMessage.delayed_literal {result})
1186 assert_block(full_message) do
1187 result = receiver.__send__(message_name, *arguments)
1188 not result
1189 end
1190 end
1191 end
1192
1193 ##
1194 # Passes if +actual+ is a boolean value.
1195 #
1196 # @example
1197 # assert_boolean(true) # -> pass
1198 # assert_boolean(nil) # -> fail
1199 def assert_boolean(actual, message=nil)
1200 _wrap_assertion do
1201 assert_block(build_message(message,
1202 "<true> or <false> expected but was\n<?>",
1203 actual)) do
1204 [true, false].include?(actual)
1205 end
1206 end
1207 end
1208
1209 ##
1210 # Passes if +actual+ is true.
1211 #
1212 # @example
1213 # assert_true(true) # -> pass
1214 # assert_true(:true) # -> fail
1215 def assert_true(actual, message=nil)
1216 _wrap_assertion do
1217 assert_block(build_message(message,
1218 "<true> expected but was\n<?>",
1219 actual)) do
1220 actual == true
1221 end
1222 end
1223 end
1224
1225 ##
1226 # Passes if +actual+ is false.
1227 #
1228 # @example
1229 # assert_false(false) # -> pass
1230 # assert_false(nil) # -> fail
1231 def assert_false(actual, message=nil)
1232 _wrap_assertion do
1233 assert_block(build_message(message,
1234 "<false> expected but was\n<?>",
1235 actual)) do
1236 actual == false
1237 end
1238 end
1239 end
1240
1241 ##
1242 # Passes if expression "+expected+ +operator+
1243 # +actual+" is true.
1244 #
1245 # @example
1246 # assert_compare(1, "<", 10) # -> pass
1247 # assert_compare(1, ">=", 10) # -> fail
1248 def assert_compare(expected, operator, actual, message=nil)
1249 _wrap_assertion do
1250 assert_send([["<", "<=", ">", ">="], :include?, operator.to_s])
1251 case operator.to_s
1252 when "<"
1253 operator_description = "less than"
1254 when "<="
1255 operator_description = "less than or equal to"
1256 when ">"
1257 operator_description = "greater than"
1258 when ">="
1259 operator_description = "greater than or equal to"
1260 end
1261 template = <<-EOT
1262 <?> #{operator} <?> should be true
1263 <?> expected #{operator_description}
1264 <?>.
1265 EOT
1266 full_message = build_message(message, template,
1267 expected, actual,
1268 expected, actual)
1269 assert_block(full_message) do
1270 expected.__send__(operator, actual)
1271 end
1272 end
1273 end
1274
1275 ##
1276 # Passes if assertion is failed in block.
1277 #
1278 # @example
1279 # assert_fail_assertion {assert_equal("A", "B")} # -> pass
1280 # assert_fail_assertion {assert_equal("A", "A")} # -> fail
1281 def assert_fail_assertion(message=nil)
1282 _wrap_assertion do
1283 full_message = build_message(message,
1284 "Failed assertion was expected.")
1285 assert_block(full_message) do
1286 begin
1287 yield
1288 false
1289 rescue AssertionFailedError
1290 true
1291 end
1292 end
1293 end
1294 end
1295
1296 ##
1297 # Passes if an exception is raised in block and its
1298 # message is +expected+.
1299 #
1300 # @example
1301 # assert_raise_message("exception") {raise "exception"} # -> pass
1302 # assert_raise_message(/exc/i) {raise "exception"} # -> pass
1303 # assert_raise_message("exception") {raise "EXCEPTION"} # -> fail
1304 # assert_raise_message("exception") {} # -> fail
1305 def assert_raise_message(expected, message=nil)
1306 _wrap_assertion do
1307 full_message = build_message(message,
1308 "<?> exception message expected " +
1309 "but none was thrown.",
1310 expected)
1311 exception = nil
1312 assert_block(full_message) do
1313 begin
1314 yield
1315 false
1316 rescue Exception => exception
1317 true
1318 end
1319 end
1320
1321 actual = exception.message
1322 diff = AssertionMessage.delayed_diff(expected, actual)
1323 full_message =
1324 build_message(message,
1325 "<?> exception message expected but was\n" +
1326 "<?>.?", expected, actual, diff)
1327 assert_block(full_message) do
1328 if expected.is_a?(Regexp)
1329 expected =~ actual
1330 else
1331 expected == actual
1332 end
1333 end
1334 end
1335 end
1336
1337 ##
1338 # Passes if +object+.const_defined?(+constant_name+)
1339 #
1340 # @example
1341 # assert_const_defined(Test, :Unit) # -> pass
1342 # assert_const_defined(Object, :Nonexistent) # -> fail
1343 def assert_const_defined(object, constant_name, message=nil)
1344 _wrap_assertion do
1345 full_message = build_message(message,
1346 "<?>.const_defined\\?(<?>) expected.",
1347 object, constant_name)
1348 assert_block(full_message) do
1349 object.const_defined?(constant_name)
1350 end
1351 end
1352 end
1353
1354 ##
1355 # Passes if !+object+.const_defined?(+constant_name+)
1356 #
1357 # @example
1358 # assert_not_const_defined(Object, :Nonexistent) # -> pass
1359 # assert_not_const_defined(Test, :Unit) # -> fail
1360 def assert_not_const_defined(object, constant_name, message=nil)
1361 _wrap_assertion do
1362 full_message = build_message(message,
1363 "!<?>.const_defined\\?(<?>) expected.",
1364 object, constant_name)
1365 assert_block(full_message) do
1366 !object.const_defined?(constant_name)
1367 end
1368 end
1369 end
1370
1371 ##
1372 # Passes if +object+.+predicate+ is _true_.
1373 #
1374 # @example
1375 # assert_predicate([], :empty?) # -> pass
1376 # assert_predicate([1], :empty?) # -> fail
1377 def assert_predicate(object, predicate, message=nil)
1378 _wrap_assertion do
1379 assert_respond_to(object, predicate, message)
1380 actual = object.__send__(predicate)
1381 full_message = build_message(message,
1382 "<?>.? is true value expected but was\n" +
1383 "<?>",
1384 object,
1385 AssertionMessage.literal(predicate),
1386 actual)
1387 assert_block(full_message) do
1388 actual
1389 end
1390 end
1391 end
1392
1393 ##
1394 # Passes if +object+.+predicate+ is not _true_.
1395 #
1396 # @example
1397 # assert_not_predicate([1], :empty?) # -> pass
1398 # assert_not_predicate([], :empty?) # -> fail
1399 def assert_not_predicate(object, predicate, message=nil)
1400 _wrap_assertion do
1401 assert_respond_to(object, predicate, message)
1402 actual = object.__send__(predicate)
1403 full_message = build_message(message,
1404 "<?>.? is false value expected but was\n" +
1405 "<?>",
1406 object,
1407 AssertionMessage.literal(predicate),
1408 actual)
1409 assert_block(full_message) do
1410 not actual
1411 end
1412 end
1413 end
1414
1415 # Just for minitest compatibility. :<
1416 #
1417 # @since 3.0.0
1418 alias_method :refute_predicate, :assert_not_predicate
1419
1420 ##
1421 # Passes if +object+#+alias_name+ is an alias method of
1422 # +object+#+original_name+.
1423 #
1424 # @example
1425 # assert_alias_method([], :length, :size) # -> pass
1426 # assert_alias_method([], :size, :length) # -> pass
1427 # assert_alias_method([], :each, :size) # -> fail
1428 def assert_alias_method(object, alias_name, original_name, message=nil)
1429 _wrap_assertion do
1430 find_method_failure_message = Proc.new do |method_name|
1431 build_message(message,
1432 "<?>.? doesn't exist\n" +
1433 "(Class: <?>)",
1434 object,
1435 AssertionMessage.literal(method_name),
1436 object.class)
1437 end
1438
1439 alias_method = original_method = nil
1440 assert_block(find_method_failure_message.call(alias_name)) do
1441 begin
1442 alias_method = object.method(alias_name)
1443 true
1444 rescue NameError
1445 false
1446 end
1447 end
1448 assert_block(find_method_failure_message.call(original_name)) do
1449 begin
1450 original_method = object.method(original_name)
1451 true
1452 rescue NameError
1453 false
1454 end
1455 end
1456
1457 full_message = build_message(message,
1458 "<?> is alias of\n" +
1459 "<?> expected",
1460 alias_method,
1461 original_method)
1462 assert_block(full_message) do
1463 alias_method == original_method
1464 end
1465 end
1466 end
1467
1468 ##
1469 # Passes if +path+ exists.
1470 #
1471 # @example
1472 # assert_path_exist("/tmp") # -> pass
1473 # assert_path_exist("/bin/sh") # -> pass
1474 # assert_path_exist("/nonexistent") # -> fail
1475 def assert_path_exist(path, message=nil)
1476 _wrap_assertion do
1477 failure_message = build_message(message,
1478 "<?> expected to exist",
1479 path)
1480 assert_block(failure_message) do
1481 File.exist?(path)
1482 end
1483 end
1484 end
1485
1486 ##
1487 # Passes if +path+ doesn't exist.
1488 #
1489 # @example
1490 # assert_path_not_exist("/nonexistent") # -> pass
1491 # assert_path_not_exist("/tmp") # -> fail
1492 # assert_path_not_exist("/bin/sh") # -> fail
1493 def assert_path_not_exist(path, message=nil)
1494 _wrap_assertion do
1495 failure_message = build_message(message,
1496 "<?> expected to not exist",
1497 path)
1498 assert_block(failure_message) do
1499 not File.exist?(path)
1500 end
1501 end
1502 end
1503
1504 ##
1505 # Passes if +collection+ includes +object+.
1506 #
1507 # @example
1508 # assert_include([1, 10], 1) # -> pass
1509 # assert_include(1..10, 5) # -> pass
1510 # assert_include([1, 10], 5) # -> fail
1511 # assert_include(1..10, 20) # -> fail
1512 def assert_include(collection, object, message=nil)
1513 _wrap_assertion do
1514 assert_respond_to(collection, :include?,
1515 "The collection must respond to :include?.")
1516 full_message = build_message(message,
1517 "<?> expected to include\n<?>.",
1518 collection,
1519 object)
1520 assert_block(full_message) do
1521 collection.include?(object)
1522 end
1523 end
1524 end
1525
1526 # Just for minitest compatibility. :<
1527 #
1528 # @since 2.5.3
1529 alias_method :assert_includes, :assert_include
1530
1531 ##
1532 # Passes if +collection+ doesn't include +object+.
1533 #
1534 # @example
1535 # assert_not_include([1, 10], 5) # -> pass
1536 # assert_not_include(1..10, 20) # -> pass
1537 # assert_not_include([1, 10], 1) # -> fail
1538 # assert_not_include(1..10, 5) # -> fail
1539 def assert_not_include(collection, object, message=nil)
1540 _wrap_assertion do
1541 assert_respond_to(collection, :include?,
1542 "The collection must respond to :include?.")
1543 full_message = build_message(message,
1544 "<?> expected to not include\n<?>.",
1545 collection,
1546 object)
1547 assert_block(full_message) do
1548 not collection.include?(object)
1549 end
1550 end
1551 end
1552
1553 # Just for minitest compatibility. :<
1554 #
1555 # @since 3.0.0
1556 alias_method :assert_not_includes, :assert_not_include
1557
1558 # Just for minitest compatibility. :<
1559 #
1560 # @since 3.0.0
1561 alias_method :refute_includes, :assert_not_include
1562
1563 ##
1564 # Passes if +object+ is empty.
1565 #
1566 # @example
1567 # assert_empty("") # -> pass
1568 # assert_empty([]) # -> pass
1569 # assert_empty({}) # -> pass
1570 # assert_empty(" ") # -> fail
1571 # assert_empty([nil]) # -> fail
1572 # assert_empty({1 => 2}) # -> fail
1573 def assert_empty(object, message=nil)
1574 _wrap_assertion do
1575 assert_respond_to(object, :empty?,
1576 "The object must respond to :empty?.")
1577 full_message = build_message(message,
1578 "<?> expected to be empty.",
1579 object)
1580 assert_block(full_message) do
1581 object.empty?
1582 end
1583 end
1584 end
1585
1586 ##
1587 # Passes if +object+ is not empty.
1588 #
1589 # @example
1590 # assert_not_empty(" ") # -> pass
1591 # assert_not_empty([nil]) # -> pass
1592 # assert_not_empty({1 => 2}) # -> pass
1593 # assert_not_empty("") # -> fail
1594 # assert_not_empty([]) # -> fail
1595 # assert_not_empty({}) # -> fail
1596 def assert_not_empty(object, message=nil)
1597 _wrap_assertion do
1598 assert_respond_to(object, :empty?,
1599 "The object must respond to :empty?.")
1600 full_message = build_message(message,
1601 "<?> expected to not be empty.",
1602 object)
1603 assert_block(full_message) do
1604 not object.empty?
1605 end
1606 end
1607 end
1608
1609 # Just for minitest compatibility. :<
1610 #
1611 # @since 3.0.0
1612 alias_method :refute_empty, :assert_not_empty
1613
1614 ##
1615 # Builds a failure message. +head+ is added before the +template+ and
1616 # +arguments+ replaces the '?'s positionally in the template.
1617 def build_message(head, template=nil, *arguments)
1618 template &&= template.chomp
1619 return AssertionMessage.new(head, template, arguments)
1620 end
1621
1622 private
1623 def _wrap_assertion(&block)
1624 @_assertion_wrapped ||= false
1625 if @_assertion_wrapped
1626 block.call
1627 else
1628 @_assertion_wrapped = true
1629 begin
1630 add_assertion
1631 block.call
1632 ensure
1633 @_assertion_wrapped = false
1634 end
1635 end
1636 end
1637
1638 public
1639 # Called whenever an assertion is made. Define this in classes
1640 # that include Test::Unit::Assertions to record assertion
1641 # counts.
1642 #
1643 # This is a public API for developers who extend test-unit.
1644 #
1645 # @return [void]
1646 def add_assertion
1647 end
1648
1649 ##
1650 # Select whether or not to use the pretty-printer. If this option is set
1651 # to false before any assertions are made, pp.rb will not be required.
1652 def self.use_pp=(value)
1653 AssertionMessage.use_pp = value
1654 end
1655
1656 private
1657 def _assert_raise(assert_expected_exception, *args, &block)
1658 _wrap_assertion do
1659 if args.last.is_a?(String)
1660 message = args.pop
1661 else
1662 message = ""
1663 end
1664
1665 assert_exception_helper = AssertExceptionHelper.new(self, args)
1666 expected = assert_exception_helper.expected_exceptions
1667 actual_exception = nil
1668 full_message = build_message(message,
1669 "<?> exception expected " +
1670 "but none was thrown.",
1671 expected)
1672 assert_block(full_message) do
1673 begin
1674 yield
1675 false
1676 rescue Exception => actual_exception
1677 true
1678 end
1679 end
1680 assert_expected_exception.call(message, assert_exception_helper,
1681 actual_exception)
1682 actual_exception
1683 end
1684 end
1685
1686 def _set_failed_information(failure, expected, actual, user_message)
1687 failure.expected = expected
1688 failure.actual = actual
1689 failure.inspected_expected = AssertionMessage.convert(expected)
1690 failure.inspected_actual = AssertionMessage.convert(actual)
1691 failure.user_message = user_message
1692 end
1693
1694 class AssertionMessage
1695 @use_pp = true
1696 class << self
1697 attr_accessor :use_pp
1698
1699 def literal(value)
1700 Literal.new(value)
1701 end
1702
1703 def delayed_literal(&block)
1704 DelayedLiteral.new(block)
1705 end
1706
1707 def maybe_container(value, &formatter)
1708 MaybeContainer.new(value, &formatter)
1709 end
1710
1711 MAX_DIFF_TARGET_STRING_SIZE = 1000
1712 def max_diff_target_string_size
1713 return @@max_diff_target_string_size if @@max_diff_target_string_size
1714
1715 size = ENV["TEST_UNIT_MAX_DIFF_TARGET_STRING_SIZE"]
1716 if size
1717 begin
1718 size = Integer(size)
1719 rescue ArgumentError
1720 size = nil
1721 end
1722 end
1723 size || MAX_DIFF_TARGET_STRING_SIZE
1724 end
1725
1726 @@max_diff_target_string_size = nil
1727 def max_diff_target_string_size=(size)
1728 @@max_diff_target_string_size = size
1729 end
1730
1731 def diff_target_string?(string)
1732 if string.respond_to?(:bytesize)
1733 string.bytesize < max_diff_target_string_size
1734 else
1735 string.size < max_diff_target_string_size
1736 end
1737 end
1738
1739 def ensure_diffable_string(string)
1740 if string.respond_to?(:encoding) and
1741 !string.encoding.ascii_compatible?
1742 string = string.dup.force_encoding("ASCII-8BIT")
1743 end
1744 string
1745 end
1746
1747 def prepare_for_diff(from, to)
1748 if !from.is_a?(String) or !to.is_a?(String)
1749 from = convert(from)
1750 to = convert(to)
1751 end
1752
1753 if diff_target_string?(from) and diff_target_string?(to)
1754 from = ensure_diffable_string(from)
1755 to = ensure_diffable_string(to)
1756 [from, to]
1757 else
1758 [nil, nil]
1759 end
1760 end
1761
1762 def delayed_diff(from, to)
1763 delayed_literal do
1764 from, to = prepare_for_diff(from, to)
1765
1766 diff = "" if from.nil? or to.nil?
1767 diff ||= Diff.readable(from, to)
1768 if /^[-+]/ !~ diff
1769 diff = ""
1770 elsif /^[ ?]/ =~ diff or /(?:.*\n){2,}/ =~ diff
1771 diff = "\n\ndiff:\n#{diff}"
1772 else
1773 diff = ""
1774 end
1775
1776 if Diff.need_fold?(diff)
1777 folded_diff = Diff.folded_readable(from, to)
1778 diff << "\n\nfolded diff:\n#{folded_diff}"
1779 end
1780
1781 diff
1782 end
1783 end
1784
1785 def convert(object)
1786 if object.is_a?(Exception)
1787 object = AssertExceptionHelper::WrappedException.new(object)
1788 end
1789 inspector = Inspector.new(object)
1790 if use_pp
1791 begin
1792 require 'pp' unless defined?(PP)
1793 begin
1794 return PP.pp(inspector, '').chomp
1795 rescue NameError
1796 end
1797 rescue LoadError
1798 self.use_pp = false
1799 end
1800 end
1801 inspector.inspect
1802 end
1803 end
1804
1805 class Inspector
1806 include Comparable
1807
1808 class << self
1809 def cached_new(object, inspected_objects)
1810 inspected_objects[object.object_id] ||=
1811 new(object, inspected_objects)
1812 end
1813
1814 @@inspector_classes = []
1815 def inspector_classes
1816 @@inspector_classes
1817 end
1818
1819 def register_inspector_class(inspector_class)
1820 @@inspector_classes << inspector_class
1821 end
1822
1823 def unregister_inspector_class(inspector_class)
1824 @@inspector_classes.delete(inspector_class)
1825 end
1826 end
1827
1828 attr_reader :object
1829 def initialize(object, inspected_objects={})
1830 @inspected_objects = inspected_objects
1831 @object = object
1832 @inspected_objects[@object.object_id] = self
1833 @inspect_target = inspect_target
1834 end
1835
1836 alias_method :native_inspect, :inspect
1837 def inspect
1838 @inspect_target.inspect
1839 end
1840
1841 def pretty_print(q)
1842 @inspect_target.pretty_print(q)
1843 end
1844
1845 def pretty_print_cycle(q)
1846 @inspect_target.pretty_print_cycle(q)
1847 end
1848
1849 def <=>(other)
1850 if other.is_a?(self.class)
1851 @object <=> other.object
1852 else
1853 @object <=> other
1854 end
1855 end
1856
1857 private
1858 def inspect_target
1859 self.class.inspector_classes.each do |inspector_class|
1860 if inspector_class.target?(@object)
1861 return inspector_class.new(@object, @inspected_objects)
1862 end
1863 end
1864 @object
1865 end
1866 end
1867
1868 class NumericInspector
1869 Inspector.register_inspector_class(self)
1870
1871 class << self
1872 def target?(object)
1873 object.is_a?(Numeric)
1874 end
1875 end
1876
1877 def initialize(numeric, inspected_objects)
1878 @inspected_objects = inspected_objects
1879 @numeric = numeric
1880 end
1881
1882 def inspect
1883 @numeric.to_s
1884 end
1885
1886 def pretty_print(q)
1887 q.text(@numeric.to_s)
1888 end
1889
1890 def pretty_print_cycle(q)
1891 q.text(@numeric.to_s)
1892 end
1893 end
1894
1895 class HashInspector
1896 Inspector.register_inspector_class(self)
1897
1898 class << self
1899 def target?(object)
1900 object.is_a?(Hash) or ENV.equal?(object)
1901 end
1902 end
1903
1904 def initialize(hash, inspected_objects)
1905 @inspected_objects = inspected_objects
1906 @hash = {}
1907 hash.each do |key, value|
1908 key = Inspector.cached_new(key, @inspected_objects)
1909 value = Inspector.cached_new(value, @inspected_objects)
1910 @hash[key] = value
1911 end
1912 end
1913
1914 def inspect
1915 @hash.inspect
1916 end
1917
1918 def pretty_print(q)
1919 q.group(1, '{', '}') do
1920 q.seplist(self, nil, :each_pair) do |k, v|
1921 q.group do
1922 q.pp(k)
1923 q.text('=>')
1924 q.group(1) do
1925 q.breakable('')
1926 q.pp(v)
1927 end
1928 end
1929 end
1930 end
1931 end
1932
1933 def pretty_print_cycle(q)
1934 @hash.pretty_print_cycle(q)
1935 end
1936
1937 def each_pair
1938 keys = @hash.keys
1939 begin
1940 keys = keys.sort # FIXME: more cleverly
1941 rescue ArgumentError
1942 end
1943 keys.each do |key|
1944 yield(key, @hash[key])
1945 end
1946 end
1947 end
1948
1949 class ArrayInspector
1950 Inspector.register_inspector_class(self)
1951
1952 class << self
1953 def target?(object)
1954 object.is_a?(Array)
1955 end
1956 end
1957
1958 def initialize(array, inspected_objects)
1959 @inspected_objects = inspected_objects
1960 @array = array.collect do |element|
1961 Inspector.cached_new(element, @inspected_objects)
1962 end
1963 end
1964
1965 def inspect
1966 @array.inspect
1967 end
1968
1969 def pretty_print(q)
1970 q.group(1, '[', ']') do
1971 q.seplist(self) do |v|
1972 q.pp(v)
1973 end
1974 end
1975 end
1976
1977 def pretty_print_cycle(q)
1978 @array.pretty_print_cycle(q)
1979 end
1980
1981 def each(&block)
1982 @array.each(&block)
1983 end
1984 end
1985
1986 class Literal
1987 def initialize(value)
1988 @value = value
1989 end
1990
1991 def inspect
1992 @value.to_s
1993 end
1994 end
1995
1996 class DelayedLiteral
1997 def initialize(value)
1998 @value = value
1999 end
2000
2001 def inspect
2002 @value.call.to_s
2003 end
2004 end
2005
2006 class MaybeContainer
2007 def initialize(value, &formatter)
2008 @value = value
2009 @formatter = formatter
2010 end
2011
2012 def inspect
2013 if @value.is_a?(Array)
2014 values = @value.collect do |value|
2015 @formatter.call(AssertionMessage.convert(value))
2016 end
2017 "[#{values.join(', ')}]"
2018 else
2019 @formatter.call(AssertionMessage.convert(@value))
2020 end
2021 end
2022 end
2023
2024 class Template
2025 def self.create(string)
2026 parts = (string ? string.scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/m) : [])
2027 self.new(parts)
2028 end
2029
2030 attr_reader :count
2031
2032 def initialize(parts)
2033 @parts = parts
2034 @count = parts.find_all{|e| e == '?'}.size
2035 end
2036
2037 def result(parameters)
2038 raise "The number of parameters does not match the number of substitutions." if(parameters.size != count)
2039 params = parameters.dup
2040 expanded_template = ""
2041 @parts.each do |part|
2042 if part == '?'
2043 encoding_safe_concat(expanded_template, params.shift)
2044 else
2045 expanded_template << part.gsub(/\\\?/m, '?')
2046 end
2047 end
2048 expanded_template
2049 end
2050
2051 private
2052 if Object.const_defined?(:Encoding)
2053 def encoding_safe_concat(buffer, parameter)
2054 if Encoding.compatible?(buffer, parameter)
2055 buffer << parameter
2056 else
2057 buffer << parameter.dup.force_encoding(buffer.encoding)
2058 end
2059 end
2060 else
2061 def encoding_safe_concat(buffer, parameter)
2062 buffer << parameter
2063 end
2064 end
2065 end
2066
2067 include Util::BacktraceFilter
2068
2069 def initialize(head, template_string, parameters)
2070 @head = head
2071 @template_string = template_string
2072 @parameters = parameters
2073 end
2074
2075 def convert(object)
2076 self.class.convert(object)
2077 end
2078
2079 def template
2080 @template ||= Template.create(@template_string)
2081 end
2082
2083 def add_period(string)
2084 (string =~ /\.\Z/ ? string : string + '.')
2085 end
2086
2087 def to_s
2088 message_parts = []
2089 if (@head)
2090 head = @head
2091 head = head.call if head.respond_to?(:call)
2092 head = head.to_s
2093 unless(head.empty?)
2094 message_parts << add_period(head)
2095 end
2096 end
2097 tail = template.result(@parameters.collect{|e| convert(e)})
2098 message_parts << tail unless(tail.empty?)
2099 message_parts.join("\n")
2100 end
2101 end
2102
2103 class AssertExceptionHelper
2104 class WrappedException
2105 attr_reader :exception
2106 def initialize(exception)
2107 @exception = exception
2108 end
2109
2110 def inspect
2111 if default_inspect?
2112 "#{@exception.class.inspect}(<#{@exception.message}>)"
2113 else
2114 @exception.inspect
2115 end
2116 end
2117
2118 def method_missing(name, *args, &block)
2119 @exception.__send__(name, *args, &block)
2120 end
2121
2122 private
2123 def default_inspect?
2124 inspect_method = @exception.method(:inspect)
2125 if inspect_method.respond_to?(:owner) and
2126 inspect_method.owner == Exception
2127 true
2128 else
2129 default_inspect_method = Exception.instance_method(:inspect)
2130 default_inspect_method.bind(@exception).call == @exception.inspect
2131 end
2132 end
2133 end
2134
2135 def initialize(test_case, expected_exceptions)
2136 @test_case = test_case
2137 @expected_exceptions = expected_exceptions
2138 @expected_classes, @expected_modules, @expected_objects =
2139 split_expected_exceptions(expected_exceptions)
2140 end
2141
2142 def expected_exceptions
2143 exceptions = @expected_exceptions.collect do |exception|
2144 if exception.is_a?(Exception)
2145 WrappedException.new(exception)
2146 else
2147 exception
2148 end
2149 end
2150 if exceptions.size == 1
2151 exceptions[0]
2152 else
2153 exceptions
2154 end
2155 end
2156
2157 def expected?(actual_exception, equality=nil)
2158 equality ||= :instance_of?
2159 expected_class?(actual_exception, equality) or
2160 expected_module?(actual_exception) or
2161 expected_object?(actual_exception)
2162 end
2163
2164 private
2165 def split_expected_exceptions(expected_exceptions)
2166 exception_modules = []
2167 exception_objects = []
2168 exception_classes = []
2169 expected_exceptions.each do |exception_type|
2170 if exception_type.instance_of?(Module)
2171 exception_modules << exception_type
2172 elsif exception_type.is_a?(Exception)
2173 exception_objects << exception_type
2174 else
2175 @test_case.__send__(:assert,
2176 Exception >= exception_type,
2177 "Should expect a class of exception, " +
2178 "#{exception_type}")
2179 exception_classes << exception_type
2180 end
2181 end
2182 [exception_classes, exception_modules, exception_objects]
2183 end
2184
2185 def expected_class?(actual_exception, equality)
2186 @expected_classes.any? do |expected_class|
2187 actual_exception.__send__(equality, expected_class)
2188 end
2189 end
2190
2191 def expected_module?(actual_exception)
2192 @expected_modules.any? do |expected_module|
2193 actual_exception.is_a?(expected_module)
2194 end
2195 end
2196
2197 def expected_object?(actual_exception)
2198 @expected_objects.any? do |expected_object|
2199 expected_object == actual_exception or
2200 fallback_exception_object_equal(expected_object, actual_exception)
2201 end
2202 end
2203
2204 def fallback_exception_object_equal(expected_object, actual_exception)
2205 owner = Util::MethodOwnerFinder.find(expected_object, :==)
2206 if owner == Kernel or owner == Exception
2207 expected_object.class == actual_exception.class and
2208 expected_object.message == actual_exception.message
2209 else
2210 false
2211 end
2212 end
2213 end
2214
2215 # :startdoc:
2216 end
2217 end
2218 end
+0
-26
vendor/test/unit/attribute-matcher.rb less more
0 module Test
1 module Unit
2 class AttributeMatcher
3 def initialize(test)
4 @test = test
5 end
6
7 def match?(expression)
8 matched = instance_eval(expression)
9 if matched.nil?
10 false
11 else
12 matched
13 end
14 end
15
16 def method_missing(name, *args)
17 if args.empty?
18 @test[name]
19 else
20 super
21 end
22 end
23 end
24 end
25 end
+0
-154
vendor/test/unit/attribute.rb less more
0 module Test
1 module Unit
2 module Attribute
3 class StringifyKeyHash < Hash
4 class << self
5 def stringify(object)
6 object.to_s
7 end
8 end
9
10 def key?(key)
11 super(self.class.stringify(key))
12 end
13
14 def [](key)
15 super(self.class.stringify(key))
16 end
17
18 def []=(key, value)
19 super(self.class.stringify(key), value)
20 end
21 end
22
23 class << self
24 def included(base)
25 base.extend(BaseClassMethods)
26 base.extend(ClassMethods)
27 end
28 end
29
30 module BaseClassMethods
31 def attributes_table
32 {}
33 end
34 end
35
36 module ClassMethods
37 def method_added(name)
38 super
39 return unless defined?(@current_attributes)
40
41 attributes = {}
42 kept_attributes = StringifyKeyHash.new
43 @current_attributes.each do |attribute_name, attribute|
44 attributes[attribute_name] = attribute[:value]
45 kept_attributes[attribute_name] = attribute if attribute[:keep]
46 end
47 set_attributes(name, attributes)
48 @current_attributes = kept_attributes
49 end
50
51 def attribute(name, value, options={}, *method_names)
52 unless options.is_a?(Hash)
53 method_names << options
54 options = {}
55 end
56 if method_names.empty?
57 current_attributes[name] = options.merge(:value => value)
58 else
59 method_names.each do |method_name|
60 set_attributes(method_name, {name => value})
61 end
62 end
63 end
64
65 def current_attributes
66 @current_attributes ||= StringifyKeyHash.new
67 end
68
69 def current_attribute(name)
70 current_attributes[name] || StringifyKeyHash.new
71 end
72
73 def attributes_table
74 @attributes_table ||= StringifyKeyHash.new
75 super.merge(@attributes_table)
76 end
77
78 def set_attributes(method_name, new_attributes)
79 return if new_attributes.empty?
80 @attributes_table ||= StringifyKeyHash.new
81 @attributes_table[method_name] ||= StringifyKeyHash.new
82 current_attributes = @attributes_table[method_name]
83 new_attributes.each do |key, value|
84 observers = attribute_observers(key) || []
85 observers.each do |observer|
86 observer.call(self,
87 StringifyKeyHash.stringify(key),
88 (attributes(method_name) || {})[key],
89 value,
90 method_name)
91 end
92 current_attributes[key] = value
93 end
94 end
95
96 def attributes(method_name)
97 attributes = attributes_table[method_name]
98 ancestors.each do |ancestor|
99 next if ancestor == self
100 if ancestor.is_a?(Class) and ancestor < Test::Unit::Attribute
101 parent_attributes = ancestor.attributes(method_name)
102 if attributes
103 attributes = (parent_attributes || {}).merge(attributes)
104 else
105 attributes = parent_attributes
106 end
107 break
108 end
109 end
110 attributes || StringifyKeyHash.new
111 end
112
113 def find_attribute(method_name, name)
114 @attributes_table ||= StringifyKeyHash.new
115 if @attributes_table.key?(method_name)
116 attributes = @attributes_table[method_name]
117 if attributes.key?(name)
118 return attributes[name]
119 end
120 end
121
122 return nil if self == TestCase
123
124 @cached_parent_test_case ||= ancestors.find do |ancestor|
125 ancestor != self and
126 ancestor.is_a?(Class) and
127 ancestor < Test::Unit::Attribute
128 end
129
130 @cached_parent_test_case.find_attribute(method_name, name)
131 end
132
133 @@attribute_observers = StringifyKeyHash.new
134 def register_attribute_observer(attribute_name, observer=Proc.new)
135 @@attribute_observers[attribute_name] ||= []
136 @@attribute_observers[attribute_name] << observer
137 end
138
139 def attribute_observers(attribute_name)
140 @@attribute_observers[attribute_name]
141 end
142 end
143
144 def attributes
145 self.class.attributes(@method_name) || StringifyKeyHash.new
146 end
147
148 def [](name)
149 self.class.find_attribute(@method_name, name)
150 end
151 end
152 end
153 end
+0
-484
vendor/test/unit/autorunner.rb less more
0 require 'test/unit/color-scheme'
1 require 'test/unit/priority'
2 require 'test/unit/attribute-matcher'
3 require 'optparse'
4
5 module Test
6 module Unit
7 class AutoRunner
8 RUNNERS = {}
9 COLLECTORS = {}
10 ADDITIONAL_OPTIONS = []
11 PREPARE_HOOKS = []
12
13 class << self
14 def register_runner(id, runner_builder=Proc.new)
15 RUNNERS[id] = runner_builder
16 RUNNERS[id.to_s] = runner_builder
17 end
18
19 def runner(id)
20 RUNNERS[id.to_s]
21 end
22
23 @@default_runner = nil
24 def default_runner
25 runner(@@default_runner)
26 end
27
28 def default_runner=(id)
29 @@default_runner = id
30 end
31
32 def register_collector(id, collector_builder=Proc.new)
33 COLLECTORS[id] = collector_builder
34 COLLECTORS[id.to_s] = collector_builder
35 end
36
37 def collector(id)
38 COLLECTORS[id.to_s]
39 end
40
41 def register_color_scheme(id, scheme)
42 ColorScheme[id] = scheme
43 end
44
45 def setup_option(option_builder=Proc.new)
46 ADDITIONAL_OPTIONS << option_builder
47 end
48
49 def prepare(hook=Proc.new)
50 PREPARE_HOOKS << hook
51 end
52
53 def run(force_standalone=false, default_dir=nil, argv=ARGV, &block)
54 r = new(force_standalone || standalone?, &block)
55 r.base = default_dir
56 r.prepare
57 r.process_args(argv)
58 r.run
59 end
60
61 def standalone?
62 return false unless("-e" == $0)
63 ObjectSpace.each_object(Class) do |klass|
64 return false if(klass < TestCase)
65 end
66 true
67 end
68
69 @@need_auto_run = true
70 def need_auto_run?
71 @@need_auto_run
72 end
73
74 def need_auto_run=(need)
75 @@need_auto_run = need
76 end
77 end
78
79 register_collector(:descendant) do |auto_runner|
80 require 'test/unit/collector/descendant'
81 collector = Collector::Descendant.new
82 collector.filter = auto_runner.filters
83 collector.collect($0.sub(/\.rb\Z/, ''))
84 end
85
86 register_collector(:load) do |auto_runner|
87 require 'test/unit/collector/load'
88 collector = Collector::Load.new
89 unless auto_runner.pattern.empty?
90 collector.patterns.replace(auto_runner.pattern)
91 end
92 unless auto_runner.exclude.empty?
93 collector.excludes.replace(auto_runner.exclude)
94 end
95 collector.base = auto_runner.base
96 collector.filter = auto_runner.filters
97 collector.collect(*auto_runner.to_run)
98 end
99
100 # JUST TEST!
101 # register_collector(:xml) do |auto_runner|
102 # require 'test/unit/collector/xml'
103 # collector = Collector::XML.new
104 # collector.filter = auto_runner.filters
105 # collector.collect(auto_runner.to_run[0])
106 # end
107
108 # deprecated
109 register_collector(:object_space) do |auto_runner|
110 require 'test/unit/collector/objectspace'
111 c = Collector::ObjectSpace.new
112 c.filter = auto_runner.filters
113 c.collect($0.sub(/\.rb\Z/, ''))
114 end
115
116 # deprecated
117 register_collector(:dir) do |auto_runner|
118 require 'test/unit/collector/dir'
119 c = Collector::Dir.new
120 c.filter = auto_runner.filters
121 unless auto_runner.pattern.empty?
122 c.pattern.replace(auto_runner.pattern)
123 end
124 unless auto_runner.exclude.empty?
125 c.exclude.replace(auto_runner.exclude)
126 end
127 c.base = auto_runner.base
128 $:.push(auto_runner.base) if auto_runner.base
129 c.collect(*(auto_runner.to_run.empty? ? ['.'] : auto_runner.to_run))
130 end
131
132 attr_reader :suite, :runner_options
133 attr_accessor :filters, :to_run, :pattern, :exclude, :base, :workdir
134 attr_accessor :color_scheme, :listeners
135 attr_writer :runner, :collector
136
137 def initialize(standalone)
138 @standalone = standalone
139 @runner = default_runner
140 @collector = default_collector
141 @filters = []
142 @to_run = []
143 @color_scheme = ColorScheme.default
144 @runner_options = {}
145 @default_arguments = []
146 @workdir = nil
147 @listeners = []
148 config_file = "test-unit.yml"
149 if File.exist?(config_file)
150 load_config(config_file)
151 else
152 load_global_config
153 end
154 yield(self) if block_given?
155 end
156
157 def prepare
158 PREPARE_HOOKS.each do |handler|
159 handler.call(self)
160 end
161 end
162
163 def process_args(args=ARGV)
164 begin
165 args.unshift(*@default_arguments)
166 options.order!(args) {|arg| @to_run << arg}
167 rescue OptionParser::ParseError => e
168 puts e
169 puts options
170 exit(false)
171 end
172 not @to_run.empty?
173 end
174
175 def options
176 @options ||= OptionParser.new do |o|
177 o.banner = "Test::Unit automatic runner."
178 o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
179
180 o.on('-r', '--runner=RUNNER', RUNNERS,
181 "Use the given RUNNER.",
182 "(" + keyword_display(RUNNERS) + ")") do |r|
183 @runner = r
184 end
185
186 o.on('--collector=COLLECTOR', COLLECTORS,
187 "Use the given COLLECTOR.",
188 "(" + keyword_display(COLLECTORS) + ")") do |collector|
189 @collector = collector
190 end
191
192 if (@standalone)
193 o.on('-b', '--basedir=DIR', "Base directory of test suites.") do |b|
194 @base = b
195 end
196
197 o.on('-w', '--workdir=DIR', "Working directory to run tests.") do |w|
198 @workdir = w
199 end
200
201 o.on('-a', '--add=TORUN', Array,
202 "Add TORUN to the list of things to run;",
203 "can be a file or a directory.") do |a|
204 @to_run.concat(a)
205 end
206
207 @pattern = []
208 o.on('-p', '--pattern=PATTERN', Regexp,
209 "Match files to collect against PATTERN.") do |e|
210 @pattern << e
211 end
212
213 @exclude = []
214 o.on('-x', '--exclude=PATTERN', Regexp,
215 "Ignore files to collect against PATTERN.") do |e|
216 @exclude << e
217 end
218 end
219
220 o.on('-n', '--name=NAME', String,
221 "Runs tests matching NAME.",
222 "Use '/PATTERN/' for NAME to use regular expression.") do |name|
223 name = (%r{\A/(.*)/\Z} =~ name ? Regexp.new($1) : name)
224 @filters << lambda do |test|
225 return true if name === test.method_name
226 test_name_without_class_name = test.name.gsub(/\(.+?\)\z/, "")
227 if test_name_without_class_name != test.method_name
228 return true if name === test_name_without_class_name
229 end
230 false
231 end
232 end
233
234 o.on('--ignore-name=NAME', String,
235 "Ignores tests matching NAME.",
236 "Use '/PATTERN/' for NAME to use regular expression.") do |n|
237 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
238 case n
239 when Regexp
240 @filters << proc {|t| n =~ t.method_name ? false : true}
241 else
242 @filters << proc {|t| n != t.method_name}
243 end
244 end
245
246 o.on('-t', '--testcase=TESTCASE', String,
247 "Runs tests in TestCases matching TESTCASE.",
248 "Use '/PATTERN/' for TESTCASE to use regular expression.") do |n|
249 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
250 @filters << lambda do |test|
251 match_test_case_name(test, n)
252 end
253 end
254
255 o.on('--ignore-testcase=TESTCASE', String,
256 "Ignores tests in TestCases matching TESTCASE.",
257 "Use '/PATTERN/' for TESTCASE to use regular expression.") do |n|
258 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
259 @filters << lambda do |test|
260 not match_test_case_name(test, n)
261 end
262 end
263
264 o.on('--location=LOCATION', String,
265 "Runs tests that defined in LOCATION.",
266 "LOCATION is one of PATH:LINE, PATH or LINE") do |location|
267 if /\A\d+\z/ =~ location
268 path = nil
269 line = location.to_i
270 else
271 path, line, = location.split(/:(\d+)/, 2)
272 line = line.to_i unless line.nil?
273 end
274 @filters << lambda do |test|
275 test.class.test_defined?(:path => path,
276 :line => line,
277 :method_name => test.method_name)
278 end
279 end
280
281 o.on('--attribute=EXPRESSION', String,
282 "Runs tests that matches EXPRESSION.",
283 "EXPRESSION is evaluated as Ruby's expression.",
284 "Test attribute name can be used with no receiver in EXPRESSION.",
285 "EXPRESSION examples:",
286 " !slow",
287 " tag == 'important' and !slow") do |expression|
288 @filters << lambda do |test|
289 matcher = AttributeMatcher.new(test)
290 matcher.match?(expression)
291 end
292 end
293
294 priority_filter = Proc.new do |test|
295 if @filters == [priority_filter]
296 Priority::Checker.new(test).need_to_run?
297 else
298 nil
299 end
300 end
301 o.on("--[no-]priority-mode",
302 "Runs some tests based on their priority.") do |priority_mode|
303 if priority_mode
304 Priority.enable
305 @filters |= [priority_filter]
306 else
307 Priority.disable
308 @filters -= [priority_filter]
309 end
310 end
311
312 o.on("--default-priority=PRIORITY",
313 Priority.available_values,
314 "Uses PRIORITY as default priority",
315 "(#{keyword_display(Priority.available_values)})") do |priority|
316 Priority.default = priority
317 end
318
319 o.on('-I', "--load-path=DIR[#{File::PATH_SEPARATOR}DIR...]",
320 "Appends directory list to $LOAD_PATH.") do |dirs|
321 $LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
322 end
323
324 color_schemes = ColorScheme.all
325 o.on("--color-scheme=SCHEME", color_schemes,
326 "Use SCHEME as color scheme.",
327 "(#{keyword_display(color_schemes)})") do |scheme|
328 @color_scheme = scheme
329 end
330
331 o.on("--config=FILE",
332 "Use YAML fomat FILE content as configuration file.") do |file|
333 load_config(file)
334 end
335
336 o.on("--order=ORDER", TestCase::AVAILABLE_ORDERS,
337 "Run tests in a test case in ORDER order.",
338 "(#{keyword_display(TestCase::AVAILABLE_ORDERS)})") do |order|
339 TestCase.test_order = order
340 end
341
342 assertion_message_class = Test::Unit::Assertions::AssertionMessage
343 o.on("--max-diff-target-string-size=SIZE", Integer,
344 "Shows diff if both expected result string size and " +
345 "actual result string size are " +
346 "less than or equal SIZE in bytes.",
347 "(#{assertion_message_class.max_diff_target_string_size})") do |size|
348 assertion_message_class.max_diff_target_string_size = size
349 end
350
351 ADDITIONAL_OPTIONS.each do |option_builder|
352 option_builder.call(self, o)
353 end
354
355 o.on('--',
356 "Stop processing options so that the",
357 "remaining options will be passed to the",
358 "test."){o.terminate}
359
360 o.on('-h', '--help', 'Display this help.'){puts o; exit}
361
362 o.on_tail
363 o.on_tail('Deprecated options:')
364
365 o.on_tail('--console', 'Console runner (use --runner).') do
366 warn("Deprecated option (--console).")
367 @runner = self.class.runner(:console)
368 end
369
370 if RUNNERS[:fox]
371 o.on_tail('--fox', 'Fox runner (use --runner).') do
372 warn("Deprecated option (--fox).")
373 @runner = self.class.runner(:fox)
374 end
375 end
376
377 o.on_tail
378 end
379 end
380
381 def keyword_display(keywords)
382 keywords = keywords.collect do |keyword, _|
383 keyword.to_s
384 end.uniq.sort
385
386 i = 0
387 keywords.collect do |keyword|
388 if (i > 0 and keyword[0] == keywords[i - 1][0]) or
389 ((i < keywords.size - 1) and (keyword[0] == keywords[i + 1][0]))
390 n = 2
391 else
392 n = 1
393 end
394 i += 1
395 keyword.sub(/^(.{#{n}})([A-Za-z]+)(?=\w*$)/, '\\1[\\2]')
396 end.join(", ")
397 end
398
399 def run
400 self.class.need_auto_run = false
401 suite = @collector[self]
402 return false if suite.nil?
403 return true if suite.empty?
404 runner = @runner[self]
405 return false if runner.nil?
406 @runner_options[:color_scheme] ||= @color_scheme
407 @runner_options[:listeners] ||= []
408 @runner_options[:listeners].concat(@listeners)
409 change_work_directory do
410 runner.run(suite, @runner_options).passed?
411 end
412 end
413
414 def load_config(file)
415 require 'yaml'
416 config = YAML.load(File.read(file))
417 runner_name = config["runner"]
418 @runner = self.class.runner(runner_name) || @runner
419 @collector = self.class.collector(config["collector"]) || @collector
420 (config["color_schemes"] || {}).each do |name, options|
421 ColorScheme[name] = options
422 end
423 runner_options = {}
424 (config["#{runner_name}_options"] || {}).each do |key, value|
425 key = key.to_sym
426 value = ColorScheme[value] if key == :color_scheme
427 if key == :arguments
428 @default_arguments.concat(value.split)
429 else
430 runner_options[key.to_sym] = value
431 end
432 end
433 @runner_options = @runner_options.merge(runner_options)
434 end
435
436 private
437 def default_runner
438 runner = self.class.default_runner
439 if ENV["EMACS"] == "t"
440 runner ||= self.class.runner(:emacs)
441 else
442 runner ||= self.class.runner(:console)
443 end
444 runner
445 end
446
447 def default_collector
448 self.class.collector(@standalone ? :load : :descendant)
449 end
450
451 def global_config_file
452 File.expand_path("~/.test-unit.yml")
453 rescue ArgumentError
454 nil
455 end
456
457 def load_global_config
458 file = global_config_file
459 load_config(file) if file and File.exist?(file)
460 end
461
462 def change_work_directory(&block)
463 if @workdir
464 Dir.chdir(@workdir, &block)
465 else
466 yield
467 end
468 end
469
470 def match_test_case_name(test, pattern)
471 test.class.ancestors.each do |test_class|
472 break if test_class == TestCase
473 return true if pattern === test_class.name
474 end
475 false
476 end
477 end
478 end
479 end
480
481 require 'test/unit/runner/console'
482 require 'test/unit/runner/emacs'
483 require 'test/unit/runner/xml'
+0
-58
vendor/test/unit/code-snippet-fetcher.rb less more
0 module Test
1 module Unit
2 class CodeSnippetFetcher
3 def initialize
4 @sources = {}
5 end
6
7 def fetch(path, line, options={})
8 n_context_line = options[:n_context_line] || 3
9 lines = source(path)
10 return [] if lines.nil?
11 min_line = [line - n_context_line, 1].max
12 max_line = [line + n_context_line, lines.length].min
13 window = min_line..max_line
14 window.collect do |n|
15 attributes = {:target_line? => (n == line)}
16 [n, lines[n - 1].chomp, attributes]
17 end
18 end
19
20 def source(path)
21 @sources[path] ||= read_source(path)
22 end
23
24 private
25 def read_source(path)
26 return nil unless File.exist?(path)
27 lines = []
28 File.open(path) do |file|
29 first_line = file.gets
30 break if first_line.nil?
31 encoding = detect_encoding(first_line)
32 if encoding
33 first_line.force_encoding(encoding)
34 file.set_encoding(encoding)
35 end
36 lines << first_line
37 lines.concat(file.readlines)
38 end
39 lines
40 end
41
42 def detect_encoding(first_line)
43 return nil unless first_line.respond_to?(:ascii_only?)
44 return nil unless first_line.ascii_only?
45 if /\b(?:en)?coding[:=]\s*([a-z\d_-]+)/i =~ first_line
46 begin
47 Encoding.find($1)
48 rescue ArgumentError
49 nil
50 end
51 else
52 nil
53 end
54 end
55 end
56 end
57 end
+0
-19
vendor/test/unit/collector/descendant.rb less more
0 require 'test/unit/collector'
1
2 module Test
3 module Unit
4 module Collector
5 class Descendant
6 include Collector
7
8 NAME = 'collected from the subclasses of TestCase'
9
10 def collect(name=NAME)
11 suite = TestSuite.new(name)
12 add_test_cases(suite, TestCase::DESCENDANTS)
13 suite
14 end
15 end
16 end
17 end
18 end
+0
-108
vendor/test/unit/collector/dir.rb less more
0 require 'test/unit/testsuite'
1 require 'test/unit/collector'
2
3 module Test
4 module Unit
5 module Collector
6 class Dir
7 include Collector
8
9 attr_reader :pattern, :exclude
10 attr_accessor :base
11
12 def initialize(dir=::Dir, file=::File, object_space=::ObjectSpace, req=nil)
13 super()
14 @dir = dir
15 @file = file
16 @object_space = object_space
17 @req = req
18 @pattern = [/\btest_.*\.rb\Z/m]
19 @exclude = []
20 @base = nil
21 end
22
23 def collect(*from)
24 basedir = @base
25 $:.push(basedir) if basedir
26 if(from.empty?)
27 recursive_collect('.', find_test_cases)
28 elsif(from.size == 1)
29 recursive_collect(from.first, find_test_cases)
30 else
31 suites = []
32 from.each do |f|
33 suite = recursive_collect(f, find_test_cases)
34 suites << suite unless(suite.tests.empty?)
35 end
36 suite = TestSuite.new("[#{from.join(', ')}]")
37 sort(suites).each{|s| suite << s}
38 suite
39 end
40 ensure
41 $:.delete_at($:.rindex(basedir)) if basedir
42 end
43
44 def find_test_cases(ignore=[])
45 cases = []
46 @object_space.each_object(Class) do |c|
47 cases << c if(c < TestCase && !ignore.include?(c))
48 end
49 ignore.concat(cases)
50 cases
51 end
52
53 def recursive_collect(name, already_gathered)
54 sub_suites = []
55 path = realdir(name)
56 if @file.directory?(path)
57 dir_name = name unless name == '.'
58 @dir.entries(path).each do |e|
59 next if(e == '.' || e == '..')
60 e_name = dir_name ? @file.join(dir_name, e) : e
61 if @file.directory?(realdir(e_name))
62 next if /\A(?:CVS|\.svn|\.git)\z/ =~ e
63 sub_suite = recursive_collect(e_name, already_gathered)
64 sub_suites << sub_suite unless(sub_suite.empty?)
65 else
66 next if /~\z/ =~ e_name or /\A\.\#/ =~ e
67 if @pattern and !@pattern.empty?
68 next unless @pattern.any? {|pat| pat =~ e_name}
69 end
70 if @exclude and !@exclude.empty?
71 next if @exclude.any? {|pat| pat =~ e_name}
72 end
73 collect_file(e_name, sub_suites, already_gathered)
74 end
75 end
76 else
77 collect_file(name, sub_suites, already_gathered)
78 end
79 suite = TestSuite.new(@file.basename(name))
80 sort(sub_suites).each{|s| suite << s}
81 suite
82 end
83
84 def collect_file(name, suites, already_gathered)
85 dir = @file.dirname(@file.expand_path(name, @base))
86 $:.unshift(dir)
87 if(@req)
88 @req.require(name)
89 else
90 require(name)
91 end
92 find_test_cases(already_gathered).each{|t| add_suite(suites, t.suite)}
93 ensure
94 $:.delete_at($:.rindex(dir)) if(dir)
95 end
96
97 def realdir(path)
98 if @base
99 @file.join(@base, path)
100 else
101 path
102 end
103 end
104 end
105 end
106 end
107 end
+0
-191
vendor/test/unit/collector/load.rb less more
0 require 'pathname'
1
2 require 'test/unit/testsuite'
3 require 'test/unit/collector'
4
5 module Test
6 module Unit
7 module Collector
8 class Load
9 include Collector
10
11 attr_reader :patterns, :excludes, :base
12
13 def initialize
14 super
15 @system_excludes = [/~\z/, /\A\.\#/]
16 @system_directory_excludes = [/\A(?:CVS|\.svn|\.git)\z/]
17 @patterns = [/\Atest[_\-].+\.rb\z/m, /[_\-]test\.rb\z/]
18 @excludes = []
19 @base = nil
20 @require_failed_infos = []
21 end
22
23 def base=(base)
24 base = Pathname(base) unless base.nil?
25 @base = base
26 end
27
28 def collect(*froms)
29 add_load_path(@base) do
30 froms = ["."] if froms.empty?
31 test_suites = []
32 already_gathered = find_test_cases
33 froms.each do |from|
34 from = resolve_path(from)
35 if from.directory?
36 test_suite = collect_recursive(from, already_gathered)
37 test_suites << test_suite unless test_suite.tests.empty?
38 else
39 collect_file(from, test_suites, already_gathered)
40 end
41 end
42 add_require_failed_test_suite(test_suites)
43
44 if test_suites.size > 1
45 test_suite = TestSuite.new("[#{froms.join(', ')}]")
46 sort(test_suites).each do |sub_test_suite|
47 test_suite << sub_test_suite
48 end
49 else
50 test_suite = test_suites.first
51 end
52
53 test_suite
54 end
55 end
56
57 def find_test_cases(ignore=[])
58 test_cases = []
59 TestCase::DESCENDANTS.each do |test_case|
60 test_cases << test_case unless ignore.include?(test_case)
61 end
62 ignore.concat(test_cases)
63 test_cases
64 end
65
66 private
67 def collect_recursive(path, already_gathered)
68 sub_test_suites = []
69
70 if path.directory?
71 directories, files = path.children.partition do |child|
72 child.directory?
73 end
74
75 files.each do |child|
76 next if excluded_file?(child.basename.to_s)
77 collect_file(child, sub_test_suites, already_gathered)
78 end
79
80 directories.each do |child|
81 next if excluded_directory?(child.basename.to_s)
82 sub_test_suite = collect_recursive(child, already_gathered)
83 sub_test_suites << sub_test_suite unless sub_test_suite.empty?
84 end
85 else
86 unless excluded_file?(path.basename.to_s)
87 collect_file(path, sub_test_suites, already_gathered)
88 end
89 end
90
91 test_suite = TestSuite.new(path.basename.to_s)
92 sort(sub_test_suites).each do |sub_test_suite|
93 test_suite << sub_test_suite
94 end
95 test_suite
96 end
97
98 def collect_file(path, test_suites, already_gathered)
99 @program_file ||= File.expand_path($0)
100 expanded_path = path.expand_path
101 return if @program_file == expanded_path.to_s
102 add_load_path(expanded_path.dirname) do
103 begin
104 require(path.basename.to_s)
105 rescue LoadError
106 @require_failed_infos << {:path => expanded_path, :exception => $!}
107 end
108 add_test_cases(test_suites, find_test_cases(already_gathered))
109 end
110 end
111
112 def resolve_path(path)
113 if @base
114 @base + path
115 else
116 Pathname(path)
117 end
118 end
119
120 def add_load_path(path)
121 return yield if path.nil?
122
123 path = path.to_s
124 begin
125 $LOAD_PATH.unshift(path)
126 yield
127 ensure
128 index = $LOAD_PATH.index(path)
129 $LOAD_PATH.delete_at(index) if index
130 end
131 end
132
133 def excluded_directory?(base)
134 @system_directory_excludes.any? {|pattern| pattern =~ base}
135 end
136
137 def excluded_file?(base)
138 return true if @system_excludes.any? {|pattern| pattern =~ base}
139
140 patterns = @patterns || []
141 unless patterns.empty?
142 return true unless patterns.any? {|pattern| pattern =~ base}
143 end
144
145 excludes = @excludes || []
146 unless excludes.empty?
147 return true if excludes.any? {|pattern| pattern =~ base}
148 end
149
150 false
151 end
152
153 def add_require_failed_test_suite(test_suites)
154 return if @require_failed_infos.empty?
155
156 require_failed_infos = @require_failed_infos
157 require_failed_omissions = Class.new(Test::Unit::TestCase)
158 require_failed_omissions.class_eval do
159 class << self
160 def name
161 "RequireFailedOmissions"
162 end
163 end
164
165 require_failed_infos.each do |info|
166 path = info[:path]
167 normalized_path = path.to_s.gsub(/[^a-z0-9\_]+/i, '_')
168 normalized_path = normalized_path.gsub(/\A_+/, '')
169 exception = info[:exception]
170 define_method("test_require_#{normalized_path}") do
171 @require_failed_exception = exception
172 omit("failed to load: <#{path}>: <#{exception.message}>")
173 end
174 end
175
176 def priority
177 100
178 end
179
180 def filter_backtrace(location)
181 super(@require_failed_exception.backtrace)
182 end
183 end
184
185 add_suite(test_suites, require_failed_omissions.suite)
186 end
187 end
188 end
189 end
190 end
+0
-34
vendor/test/unit/collector/objectspace.rb less more
0 # Author:: Nathaniel Talbott.
1 # Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
2 # License:: Ruby license.
3
4 require 'test/unit/collector'
5
6 module Test
7 module Unit
8 module Collector
9 class ObjectSpace
10 include Collector
11
12 NAME = 'collected from the ObjectSpace'
13
14 def initialize(source=::ObjectSpace)
15 super()
16 @source = source
17 end
18
19 def collect(name=NAME)
20 suite = TestSuite.new(name)
21 sub_suites = []
22 @source.each_object(Class) do |klass|
23 if(Test::Unit::TestCase > klass)
24 add_suite(sub_suites, klass.suite)
25 end
26 end
27 sort(sub_suites).each{|s| suite << s}
28 suite
29 end
30 end
31 end
32 end
33 end
+0
-250
vendor/test/unit/collector/xml.rb less more
0 #--
1 #
2 # Author:: Kouhei Sutou
3 # Copyright::
4 # * Copyright (c) 2011 Kouhei Sutou <kou@clear-code.com>
5 # License:: Ruby license.
6
7 # just test!!! don't use it yet!!!
8
9 require 'test/unit/collector'
10
11 require 'rexml/document'
12 require 'rexml/streamlistener'
13
14 module Test
15 module Unit
16 module Collector
17 class XML
18 include Collector
19
20 def collect(xml_log_path)
21 listener = Listener.new
22 File.open(xml_log_path) do |xml_log|
23 parser = REXML::Parsers::StreamParser.new(xml_log, listener)
24 parser.parse
25 end
26 suite = TestSuite.new("tests in #{xml_log_path}")
27 suites = listener.test_suites
28 sort(suites).each {|s| add_suite(suite, s)}
29 suite
30 end
31
32 class Listener
33 include REXML::StreamListener
34
35 attr_reader :test_suites
36 def initialize
37 @ns_stack = [{"xml" => :xml}]
38 @tag_stack = [["", :root]]
39 @text_stack = ['']
40 @state_stack = [:root]
41 @values = {}
42 @test_suites = []
43 end
44
45 def tag_start(name, attributes)
46 @text_stack.push('')
47
48 ns = @ns_stack.last.dup
49 attrs = {}
50 attributes.each do |n, v|
51 if /\Axmlns(?:\z|:)/ =~ n
52 ns[$POSTMATCH] = v
53 else
54 attrs[n] = v
55 end
56 end
57 @ns_stack.push(ns)
58
59 _parent_tag = parent_tag
60 prefix, local = split_name(name)
61 uri = _ns(ns, prefix)
62 @tag_stack.push([uri, local])
63
64 state = next_state(@state_stack.last, uri, local)
65 @state_stack.push(state)
66 case state
67 when :test_suite, :test_case
68 @values = {}
69 when :test
70 @values = {}
71 @n_pass_assertions = 0 if _parent_tag == "start-test"
72 when :backtrace
73 @backtrace = []
74 @values_backup = @values
75 @values = {}
76 end
77 end
78
79 def tag_end(name)
80 state = @state_stack.pop
81 text = @text_stack.pop
82 uri, local = @tag_stack.pop
83 no_action_states = [:root, :stream]
84 case state
85 when *no_action_states
86 # do nothing
87 when :test_suite
88 test_suite_end
89 when :complete_test_case
90 @test_suites.last << @test_case.suite
91 when :test_case
92 test_case_end
93 when :result
94 @result = @values
95 when :test
96 test_end
97 when :pass_assertion
98 @n_pass_assertions += 1
99 when :backtrace
100 @values = @values_backup
101 @values["backtrace"] = @backtrace
102 when :entry
103 file = @values['file']
104 line = @values['line']
105 info = @values['info']
106 @backtrace << "#{file}:#{line}: #{info}"
107 @values = {}
108 else
109 local = normalize_local(local)
110 @values[local] = text
111 end
112 @ns_stack.pop
113 end
114
115 def text(data)
116 @text_stack.last << data
117 end
118
119 private
120 def _ns(ns, prefix)
121 ns.fetch(prefix, "")
122 end
123
124 NAME_SPLIT = /^(?:([\w:][-\w\d.]*):)?([\w:][-\w\d.]*)/
125 def split_name(name)
126 name =~ NAME_SPLIT
127 [$1 || '', $2]
128 end
129
130 STATE_TABLE = {
131 :root => [:stream],
132 :stream => [:ready_test_suite,
133 :start_test_suite,
134 :ready_test_case,
135 :start_test_case,
136 :start_test,
137 :pass_assertion,
138 :test_result,
139 :complete_test,
140 :complete_test_case,
141 :complete_test_suite,
142 :success],
143 :ready_test_suite => [:n_tests],
144 :start_test_suite => [:test_suite],
145 :ready_test_case => [:test_case,
146 :n_tests],
147 :start_test_case => [:test_case],
148 :start_test => [:test],
149 :pass_assertion => [:test],
150 :complete_test => [:test, :success],
151 :complete_test_case => [:test_case,
152 :elapsed,
153 :success],
154 :complete_test_suite => [:test_suite,
155 :success],
156 :test_suite => [:start_time,
157 :elapsed],
158 :test_case => [:name,
159 :start_time,
160 :elapsed],
161 :test => [:name,
162 :start_time,
163 :elapsed],
164 :test_result => [:test,
165 :result],
166 :result => [:test_case,
167 :test,
168 :status,
169 :backtrace,
170 :detail],
171 :backtrace => [:entry],
172 :entry => [:file,
173 :line,
174 :info],
175 }
176 def next_state(current_state, uri, local)
177 local = normalize_local(local)
178 valid_elements = STATE_TABLE[current_state]
179 if valid_elements.nil?
180 raise "unexpected element: #{current_path}"
181 end
182 next_state = local.to_sym
183 unless valid_elements.include?(next_state)
184 raise "unexpected element: #{current_path}"
185 end
186 next_state
187 end
188
189 def current_path
190 locals = @tag_stack.collect do |uri, local|
191 local
192 end
193 ["", *locals].join("/")
194 end
195
196 def normalize_local(local)
197 local.gsub(/-/, "_")
198 end
199
200 def parent_tag
201 @tag_stack.last[1]
202 end
203
204 def test_suite_end
205 return unless parent_tag == "start-test-suite"
206 suite = TestSuite.new
207 ["start_time", "elapsed_time", "n_tests"].each do |key|
208 if @values.has_key?(key)
209 suite.instance_variable_set("@#{key}", @values[key])
210 end
211 end
212 @test_suites << suite
213 end
214
215 def test_case_end
216 return unless parent_tag == "start-test-case"
217 name = @values["name"]
218 @test_case = Class.new(TestCase) do
219 define_method(:name) do
220 name
221 end
222 end
223 end
224
225 def test_end
226 return unless parent_tag == "complete-test"
227 name = @values["name"]
228 n_pass_assertions = @n_pass_assertions
229 result = @result
230 @test_case.module_eval do
231 test
232 define_method(name) do
233 n_pass_assertions.times do
234 add_assertion
235 end
236 case result["status"]
237 when "omission"
238 add_omission(Omission.new(name,
239 result["backtrace"],
240 result["detail"]))
241 end
242 end
243 end
244 end
245 end
246 end
247 end
248 end
249 end
+0
-73
vendor/test/unit/collector.rb less more
0 module Test
1 module Unit
2 module Collector
3 def initialize
4 @filters = []
5 end
6
7 def filter=(filters)
8 @filters = case(filters)
9 when Proc
10 [filters]
11 when Array
12 filters
13 end
14 end
15
16 def add_suite(destination, suite)
17 to_delete = suite.tests.find_all do |test|
18 test.is_a?(TestCase) and !include?(test)
19 end
20 suite.delete_tests(to_delete)
21 destination << suite unless suite.empty?
22 end
23
24 def add_test_cases(suite, test_cases)
25 children_map = {}
26 test_cases.each do |test_case|
27 ancestor_classes = test_case.ancestors.find_all do |ancestor|
28 ancestor.is_a?(Class)
29 end
30 parent = ancestor_classes[1]
31 children_map[parent] ||= []
32 children_map[parent] << test_case
33 end
34
35 root_test_cases = children_map.keys - test_cases
36 root_test_cases.each do |root_test_case|
37 add_test_case(suite, root_test_case, children_map)
38 end
39 end
40
41 def include?(test)
42 return true if(@filters.empty?)
43 @filters.each do |filter|
44 return false if filter[test] == false
45 end
46 true
47 end
48
49 def sort(suites)
50 suites.sort_by do |suite|
51 [suite.priority, suite.name || suite.to_s]
52 end
53 end
54
55 private
56 def add_test_case(suite, test_case, children_map)
57 children = children_map[test_case]
58 return if children.nil?
59
60 sub_suites = []
61 children.each do |child|
62 sub_suite = child.suite
63 add_test_case(sub_suite, child, children_map)
64 add_suite(sub_suites, sub_suite)
65 end
66 sort(sub_suites).each do |sub_suite|
67 suite << sub_suite
68 end
69 end
70 end
71 end
72 end
+0
-198
vendor/test/unit/color-scheme.rb less more
0 require 'test/unit/color'
1
2 module Test
3 module Unit
4 class ColorScheme
5 include Enumerable
6
7 class << self
8 def default
9 if available_colors == 256
10 default_for_256_colors
11 else
12 default_for_8_colors
13 end
14 end
15
16 @@default_for_8_colors = nil
17 def default_for_8_colors
18 @@default_for_8_colors ||=
19 new("pass" => Color.new("green", :background => true) +
20 Color.new("white", :bold => true),
21 "pass-marker" => Color.new("green", :bold => true),
22 "failure" => Color.new("red", :background => true) +
23 Color.new("white", :bold => true),
24 "failure-marker" => Color.new("red"),
25 "pending" => Color.new("magenta", :background => true) +
26 Color.new("white", :bold => true),
27 "pending-marker" => Color.new("magenta"),
28 "omission" => Color.new("blue", :background => true) +
29 Color.new("white", :bold => true),
30 "omission-marker" => Color.new("blue"),
31 "notification" => Color.new("cyan", :background => true) +
32 Color.new("white", :bold => true),
33 "notification-marker" => Color.new("cyan"),
34 "error" => Color.new("black", :background => true) +
35 Color.new("yellow", :bold => true),
36 "error-marker" => Color.new("yellow"),
37 "case" => Color.new("blue", :background => true) +
38 Color.new("white", :bold => true),
39 "suite" => Color.new("green", :background => true) +
40 Color.new("white", :bold => true),
41 "diff-inserted-tag" => Color.new("red", :background => true) +
42 Color.new("black", :bold => true),
43 "diff-deleted-tag" => Color.new("green", :background => true) +
44 Color.new("black", :bold => true),
45 "diff-difference-tag" => Color.new("cyan", :background => true) +
46 Color.new("white", :bold => true),
47 "diff-inserted" => Color.new("red", :background => true) +
48 Color.new("white", :bold => true),
49 "diff-deleted" => Color.new("green", :background => true) +
50 Color.new("white", :bold => true))
51 end
52
53 @@default_for_256_colors = nil
54 def default_for_256_colors
55 @@default_for_256_colors ||=
56 new("pass" => Color.new("030", :background => true) +
57 Color.new("555", :bold => true),
58 "pass-marker" => Color.new("050", :bold => true),
59 "failure" => Color.new("300", :background => true) +
60 Color.new("555", :bold => true),
61 "failure-marker" => Color.new("500"),
62 "pending" => Color.new("303", :background => true) +
63 Color.new("555", :bold => true),
64 "pending-marker" => Color.new("303"),
65 "omission" => Color.new("001", :background => true) +
66 Color.new("555", :bold => true),
67 "omission-marker" => Color.new("001"),
68 "notification" => Color.new("011", :background => true) +
69 Color.new("555", :bold => true),
70 "notification-marker" => Color.new("011"),
71 "error" => Color.new("000", :background => true) +
72 Color.new("550", :bold => true),
73 "error-marker" => Color.new("550"),
74 "case" => Color.new("220", :background => true) +
75 Color.new("555", :bold => true),
76 "suite" => Color.new("110", :background => true) +
77 Color.new("555", :bold => true),
78 "diff-inserted-tag" => Color.new("500", :background => true) +
79 Color.new("000", :bold => true),
80 "diff-deleted-tag" => Color.new("050", :background => true) +
81 Color.new("000", :bold => true),
82 "diff-difference-tag" => Color.new("005", :background => true) +
83 Color.new("555", :bold => true),
84 "diff-inserted" => Color.new("300", :background => true) +
85 Color.new("555", :bold => true),
86 "diff-deleted" => Color.new("030", :background => true) +
87 Color.new("555", :bold => true))
88 end
89
90 @@schemes = {}
91 def all
92 @@schemes.merge("default" => default)
93 end
94
95 def [](id)
96 @@schemes[id.to_s]
97 end
98
99 def []=(id, scheme_or_spec)
100 if scheme_or_spec.is_a?(self)
101 scheme = scheme_or_spec
102 else
103 scheme = new(scheme_or_spec)
104 end
105 @@schemes[id.to_s] = scheme
106 end
107
108 def available_colors
109 guess_available_colors_from_vte_version_env ||
110 guess_available_colors_from_colorterm_env ||
111 guess_available_colors_from_term_env ||
112 8
113 end
114
115 private
116 def guess_available_colors_from_vte_version_env
117 vte_version = ENV["VTE_VERSION"]
118 return nil if vte_version.nil?
119
120 major = 0
121 minor = 13
122 micro = 0
123 packed_version = major * 10000 + minor * 100 + micro
124 if vte_version.to_i >= packed_version
125 256
126 else
127 8
128 end
129 end
130
131 def guess_available_colors_from_colorterm_env
132 case ENV["COLORTERM"]
133 when "gnome-terminal", "xfce4-terminal"
134 256
135 else
136 nil
137 end
138 end
139
140 def guess_available_colors_from_term_env
141 case ENV["TERM"]
142 when /-256color\z/
143 256
144 else
145 nil
146 end
147 end
148 end
149
150 def initialize(scheme_spec)
151 @scheme = {}
152 scheme_spec.each do |key, color_spec|
153 self[key] = color_spec
154 end
155 end
156
157 def [](name)
158 @scheme[name.to_s]
159 end
160
161 def []=(name, color_spec)
162 @scheme[name.to_s] = make_color(color_spec)
163 end
164
165 def each(&block)
166 @scheme.each(&block)
167 end
168
169 def to_hash
170 hash = {}
171 @scheme.each do |key, color|
172 hash[key] = color
173 end
174 hash
175 end
176
177 private
178 def make_color(color_spec)
179 if color_spec.is_a?(Color) or color_spec.is_a?(MixColor)
180 color_spec
181 else
182 color_name = nil
183 normalized_color_spec = {}
184 color_spec.each do |key, value|
185 key = key.to_sym
186 if key == :name
187 color_name = value
188 else
189 normalized_color_spec[key] = value
190 end
191 end
192 Color.new(color_name, normalized_color_spec)
193 end
194 end
195 end
196 end
197 end
+0
-134
vendor/test/unit/color.rb less more
0 module Test
1 module Unit
2 class Color
3 class Error < StandardError
4 end
5
6 class ParseError < Error
7 end
8
9 class << self
10 def parse_256_color(string)
11 case string
12 when /\A([0-5])([0-5])([0-5])\z/
13 red, green, blue = $1, $2, $3
14 red.to_i * 36 + green.to_i * 6 + blue.to_i + 16
15 else
16 message = "must be 'RGB' format and R, G and B " +
17 "are in 0-5: #{string.inspect}"
18 raise ParseError, message
19 end
20 end
21 end
22
23 NAMES = ["black", "red", "green", "yellow",
24 "blue", "magenta", "cyan", "white"]
25
26 attr_reader :name
27 def initialize(name, options={})
28 @name = name
29 if options.has_key?(:foreground)
30 if options[:foreground].nil?
31 @background = false
32 else
33 @background = !options[:foreground]
34 end
35 else
36 @background = options[:background]
37 end
38 @intensity = options[:intensity]
39 @bold = options[:bold]
40 @italic = options[:italic]
41 @underline = options[:underline]
42 end
43
44 def foreground?
45 not background?
46 end
47
48 def background?
49 @background
50 end
51
52 def intensity?
53 @intensity
54 end
55
56 def bold?
57 @bold
58 end
59
60 def italic?
61 @italic
62 end
63
64 def underline?
65 @underline
66 end
67
68 def ==(other)
69 self.class === other and
70 [name, background?, intensity?,
71 bold?, italic?, underline?] ==
72 [other.name, other.background?, other.intensity?,
73 other.bold?, other.italic?, other.underline?]
74 end
75
76 def sequence
77 sequence = []
78 if @name == "none"
79 elsif @name == "reset"
80 sequence << "0"
81 else
82 if NAMES.include?(@name)
83 color_parameter = foreground? ? 3 : 4
84 color_parameter += 6 if intensity?
85 color = NAMES.index(@name)
86 sequence << "#{color_parameter}#{color}"
87 else
88 sequence << (foreground? ? "38" : "48")
89 sequence << "5"
90 sequence << self.class.parse_256_color(@name).to_s
91 end
92 end
93 sequence << "1" if bold?
94 sequence << "3" if italic?
95 sequence << "4" if underline?
96 sequence
97 end
98
99 def escape_sequence
100 "\e[#{sequence.join(';')}m"
101 end
102
103 def +(other)
104 MixColor.new([self, other])
105 end
106 end
107
108 class MixColor
109 attr_reader :colors
110 def initialize(colors)
111 @colors = colors
112 end
113
114 def sequence
115 @colors.inject([]) do |result, color|
116 result + color.sequence
117 end
118 end
119
120 def escape_sequence
121 "\e[#{sequence.join(';')}m"
122 end
123
124 def +(other)
125 self.class.new([self, other])
126 end
127
128 def ==(other)
129 self.class === other and colors == other.colors
130 end
131 end
132 end
133 end
+0
-262
vendor/test/unit/data.rb less more
0 module Test
1 module Unit
2 module Data
3 class << self
4 def included(base)
5 base.extend(ClassMethods)
6 end
7 end
8
9 module ClassMethods
10 # This method provides Data-Driven-Test functionality.
11 #
12 # Define test data in the test code.
13 #
14 # @overload data(label, data)
15 # @param [String] label specify test case name.
16 # @param data specify test data.
17 #
18 # @example data(label, data)
19 # data("empty string", [true, ""])
20 # data("plain string", [false, "hello"])
21 # def test_empty?(data)
22 # expected, target = data
23 # assert_equal(expected, target.empty?)
24 # end
25 #
26 # @overload data(data_set)
27 # @param [Hash] data_set specify test data as a Hash that
28 # key is test label and value is test data.
29 #
30 # @example data(data_set)
31 # data("empty string" => [true, ""],
32 # "plain string" => [false, "hello"])
33 # def test_empty?(data)
34 # expected, target = data
35 # assert_equal(expected, target.empty?)
36 # end
37 #
38 # @overload data(&block)
39 # @yieldreturn [Hash] return test data set as a Hash that
40 # key is test label and value is test data.
41 #
42 # @example data(&block)
43 # data do
44 # data_set = {}
45 # data_set["empty string"] = [true, ""]
46 # data_set["plain string"] = [false, "hello"]
47 # data_set
48 # end
49 # def test_empty?(data)
50 # expected, target = data
51 # assert_equal(expected, target.empty?)
52 # end
53 #
54 def data(*arguments, &block)
55 n_arguments = arguments.size
56 case n_arguments
57 when 0
58 raise ArgumentError, "no block is given" unless block_given?
59 data_set = block
60 when 1
61 data_set = arguments[0]
62 when 2
63 data_set = {arguments[0] => arguments[1]}
64 else
65 message = "wrong number arguments(#{n_arguments} for 1..2)"
66 raise ArgumentError, message
67 end
68 current_data = current_attribute(:data)[:value] || []
69 attribute(:data, current_data + [data_set])
70 end
71
72 # This method provides Data-Driven-Test functionality.
73 #
74 # Load test data from the file. This is shorthand to load
75 # test data from file. If you want to load complex file, you
76 # can use {#data} with block.
77 #
78 # @param [String] file_name full path to test data file.
79 # File format is automatically detected from filename extension.
80 # @raise [ArgumentError] if +file_name+ is not supported file format.
81 # @see Loader#load
82 #
83 # @example Load data from CSV file
84 # load_data("/path/to/test-data.csv")
85 # def test_empty?(data)
86 # assert_equal(data["expected"], data["target"].empty?)
87 # end
88 #
89 def load_data(file_name)
90 loader = Loader.new(self)
91 loader.load(file_name)
92 end
93
94 class Loader
95 # @api private
96 def initialize(test_case)
97 @test_case = test_case
98 end
99
100 # Load data from file.
101 #
102 # @param [String] file_name full path to test data file.
103 # File format is automatically detected from filename extension.
104 # @raise [ArgumentError] if +file_name+ is not supported file format.
105 # @see #load_csv
106 # @see #load_tsv
107 # @api private
108 def load(file_name)
109 case File.extname(file_name).downcase
110 when ".csv"
111 load_csv(file_name)
112 when ".tsv"
113 load_tsv(file_name)
114 else
115 raise ArgumentError, "unsupported file format: <#{file_name}>"
116 end
117 end
118
119 # Load data from CSV file.
120 #
121 # There are 2 types of CSV file as following examples.
122 # First, there is a header on first row and it's first column is "label".
123 # Another, there is no header in the file.
124 #
125 # @example Load data from CSV file with header
126 # # test-data.csv:
127 # # label,expected,target
128 # # empty string,true,""
129 # # plain string,false,hello
130 # #
131 # load_data("/path/to/test-data.csv")
132 # def test_empty?(data)
133 # assert_equal(data["expected"], data["target"].empty?)
134 # end
135 #
136 # @example Load data from CSV file without header
137 # # test-data-without-header.csv:
138 # # empty string,true,""
139 # # plain string,false,hello
140 # #
141 # load_data("/path/to/test-data-without-header.csv")
142 # def test_empty?(data)
143 # expected, target = data
144 # assert_equal(expected, target.empty?)
145 # end
146 #
147 # @api private
148 def load_csv(file_name)
149 require 'csv'
150 first_row = true
151 header = nil
152 CSV.foreach(file_name) do |row|
153 if first_row
154 first_row = false
155 if row.first == "label"
156 header = row[1..-1]
157 next
158 end
159 end
160
161 set_test_data(header, row)
162 end
163 end
164
165 # Load data from TSV file.
166 #
167 # There are 2 types of TSV file as following examples.
168 # First, there is a header on first row and it's first column is "label".
169 # Another, there is no header in the file.
170 #
171 # @example Load data from TSV file with header
172 # # test-data.tsv:
173 # # label expected target
174 # # empty string true ""
175 # # plain string false hello
176 # #
177 # load_data("/path/to/test-data.tsv")
178 # def test_empty?(data)
179 # assert_equal(data["expected"], data["target"].empty?)
180 # end
181 #
182 # @example Load data from TSV file without header
183 # # test-data-without-header.tsv:
184 # # empty string true ""
185 # # plain string false hello
186 # #
187 # load_data("/path/to/test-data-without-header.tsv")
188 # def test_empty?(data)
189 # expected, target = data
190 # assert_equal(expected, target.empty?)
191 # end
192 #
193 # @api private
194 def load_tsv(file_name)
195 require "csv"
196 if CSV.const_defined?(:VERSION)
197 first_row = true
198 header = nil
199 CSV.foreach(file_name, :col_sep => "\t") do |row|
200 if first_row
201 first_row = false
202 if row.first == "label"
203 header = row[1..-1]
204 next
205 end
206 end
207
208 set_test_data(header, row)
209 end
210 else
211 # for old CSV library
212 first_row = true
213 header = nil
214 CSV.open(file_name, "r", "\t") do |row|
215 if first_row
216 first_row = false
217 if row.first == "label"
218 header = row[1..-1]
219 next
220 end
221 end
222
223 set_test_data(header, row)
224 end
225 end
226 end
227
228 private
229 def normalize_value(value)
230 return true if value == "true"
231 return false if value == "false"
232 begin
233 Integer(value)
234 rescue ArgumentError
235 begin
236 Float(value)
237 rescue ArgumentError
238 value
239 end
240 end
241 end
242
243 def set_test_data(header, row)
244 label = row.shift
245 if header
246 data = {}
247 header.each_with_index do |key, i|
248 data[key] = normalize_value(row[i])
249 end
250 else
251 data = row.collect do |cell|
252 normalize_value(cell)
253 end
254 end
255 @test_case.data(label, data)
256 end
257 end
258 end
259 end
260 end
261 end
+0
-746
vendor/test/unit/diff.rb less more
0 # port of Python's difflib.
1 #
2 # Copyright (c) 2001-2008 Python Software Foundation; All Rights Reserved
3 # Copyright (c) 2008-2011 Kouhei Sutou; All Rights Reserved
4 #
5 # It is free software, and is distributed under the Ruby
6 # license and/or the PSF license. See the COPYING file and
7 # PSFL file.
8
9 module Test
10 module Unit
11 module Diff
12 class SequenceMatcher
13 def initialize(from, to, &junk_predicate)
14 @from = from
15 @to = to
16 @junk_predicate = junk_predicate
17 update_to_indexes
18 end
19
20 def longest_match(from_start, from_end, to_start, to_end)
21 best_info = find_best_match_position(from_start, from_end,
22 to_start, to_end)
23 unless @junks.empty?
24 args = [from_start, from_end, to_start, to_end]
25 best_info = adjust_best_info_with_junk_predicate(false, best_info,
26 *args)
27 best_info = adjust_best_info_with_junk_predicate(true, best_info,
28 *args)
29 end
30
31 best_info
32 end
33
34 def blocks
35 @blocks ||= compute_blocks
36 end
37
38 def operations
39 @operations ||= compute_operations
40 end
41
42 def grouped_operations(context_size=nil)
43 context_size ||= 3
44 _operations = operations.dup
45 _operations = [[:equal, 0, 0, 0, 0]] if _operations.empty?
46 expand_edge_equal_operations!(_operations, context_size)
47
48 group_window = context_size * 2
49 groups = []
50 group = []
51 _operations.each do |tag, from_start, from_end, to_start, to_end|
52 if tag == :equal and from_end - from_start > group_window
53 group << [tag,
54 from_start,
55 [from_end, from_start + context_size].min,
56 to_start,
57 [to_end, to_start + context_size].min]
58 groups << group
59 group = []
60 from_start = [from_start, from_end - context_size].max
61 to_start = [to_start, to_end - context_size].max
62 end
63 group << [tag, from_start, from_end, to_start, to_end]
64 end
65 groups << group unless group.empty?
66 groups
67 end
68
69 def ratio
70 @ratio ||= compute_ratio
71 end
72
73 private
74 def update_to_indexes
75 @to_indexes = {}
76 @junks = {}
77 if @to.is_a?(String)
78 each = " "[0].is_a?(Integer) ? :each_byte : :each_char
79 else
80 each = :each
81 end
82 i = 0
83 @to.__send__(each) do |item|
84 @to_indexes[item] ||= []
85 @to_indexes[item] << i
86 i += 1
87 end
88
89 return if @junk_predicate.nil?
90 @to_indexes = @to_indexes.reject do |key, value|
91 junk = @junk_predicate.call(key)
92 @junks[key] = true if junk
93 junk
94 end
95 end
96
97 def find_best_match_position(from_start, from_end, to_start, to_end)
98 best_from, best_to, best_size = from_start, to_start, 0
99 sizes = {}
100 from_start.upto(from_end) do |from_index|
101 _sizes = {}
102 (@to_indexes[@from[from_index]] || []).each do |to_index|
103 next if to_index < to_start
104 break if to_index > to_end
105 size = _sizes[to_index] = (sizes[to_index - 1] || 0) + 1
106 if size > best_size
107 best_from = from_index - size + 1
108 best_to = to_index - size + 1
109 best_size = size
110 end
111 end
112 sizes = _sizes
113 end
114 [best_from, best_to, best_size]
115 end
116
117 def adjust_best_info_with_junk_predicate(should_junk, best_info,
118 from_start, from_end,
119 to_start, to_end)
120 best_from, best_to, best_size = best_info
121 while best_from > from_start and best_to > to_start and
122 (should_junk ?
123 @junks.has_key?(@to[best_to - 1]) :
124 !@junks.has_key?(@to[best_to - 1])) and
125 @from[best_from - 1] == @to[best_to - 1]
126 best_from -= 1
127 best_to -= 1
128 best_size += 1
129 end
130
131 while best_from + best_size < from_end and
132 best_to + best_size < to_end and
133 (should_junk ?
134 @junks.has_key?(@to[best_to + best_size]) :
135 !@junks.has_key?(@to[best_to + best_size])) and
136 @from[best_from + best_size] == @to[best_to + best_size]
137 best_size += 1
138 end
139
140 [best_from, best_to, best_size]
141 end
142
143 def matches
144 @matches ||= compute_matches
145 end
146
147 def compute_matches
148 matches = []
149 queue = [[0, @from.size, 0, @to.size]]
150 until queue.empty?
151 from_start, from_end, to_start, to_end = queue.pop
152 match = longest_match(from_start, from_end - 1, to_start, to_end - 1)
153 match_from_index, match_to_index, size = match
154 unless size.zero?
155 if from_start < match_from_index and
156 to_start < match_to_index
157 queue.push([from_start, match_from_index,
158 to_start, match_to_index])
159 end
160 matches << match
161 if match_from_index + size < from_end and
162 match_to_index + size < to_end
163 queue.push([match_from_index + size, from_end,
164 match_to_index + size, to_end])
165 end
166 end
167 end
168 matches.sort_by do |(from_index, _, _)|
169 from_index
170 end
171 end
172
173 def compute_blocks
174 blocks = []
175 current_from_index = current_to_index = current_size = 0
176 matches.each do |from_index, to_index, size|
177 if current_from_index + current_size == from_index and
178 current_to_index + current_size == to_index
179 current_size += size
180 else
181 unless current_size.zero?
182 blocks << [current_from_index, current_to_index, current_size]
183 end
184 current_from_index = from_index
185 current_to_index = to_index
186 current_size = size
187 end
188 end
189 unless current_size.zero?
190 blocks << [current_from_index, current_to_index, current_size]
191 end
192
193 blocks << [@from.size, @to.size, 0]
194 blocks
195 end
196
197 def compute_operations
198 from_index = to_index = 0
199 operations = []
200 blocks.each do |match_from_index, match_to_index, size|
201 tag = determine_tag(from_index, to_index,
202 match_from_index, match_to_index)
203 if tag != :equal
204 operations << [tag,
205 from_index, match_from_index,
206 to_index, match_to_index]
207 end
208
209 from_index, to_index = match_from_index + size, match_to_index + size
210 if size > 0
211 operations << [:equal,
212 match_from_index, from_index,
213 match_to_index, to_index]
214 end
215 end
216 operations
217 end
218
219 def compute_ratio
220 matches = blocks.inject(0) {|result, block| result + block[-1]}
221 length = @from.length + @to.length
222 if length.zero?
223 1.0
224 else
225 2.0 * matches / length
226 end
227 end
228
229 def determine_tag(from_index, to_index,
230 match_from_index, match_to_index)
231 if from_index < match_from_index and to_index < match_to_index
232 :replace
233 elsif from_index < match_from_index
234 :delete
235 elsif to_index < match_to_index
236 :insert
237 else
238 :equal
239 end
240 end
241
242 def expand_edge_equal_operations!(_operations, context_size)
243 tag, from_start, from_end, to_start, to_end = _operations[0]
244 if tag == :equal
245 _operations[0] = [tag,
246 [from_start, from_end - context_size].max,
247 from_end,
248 [to_start, to_end - context_size].max,
249 to_end]
250 end
251
252 tag, from_start, from_end, to_start, to_end = _operations[-1]
253 if tag == :equal
254 _operations[-1] = [tag,
255 from_start,
256 [from_end, from_start + context_size].min,
257 to_start,
258 [to_end, to_start + context_size].min]
259 end
260 end
261 end
262
263 class Differ
264 def initialize(from, to)
265 @from = from
266 @to = to
267 end
268
269 private
270 def tag(mark, contents)
271 contents.collect {|content| "#{mark}#{content}"}
272 end
273 end
274
275 class UTF8Line
276 class << self
277 # from http://unicode.org/reports/tr11/
278 WIDE_CHARACTERS =
279 [0x1100..0x1159, 0x115F..0x115F, 0x2329..0x232A,
280 0x2E80..0x2E99, 0x2E9B..0x2EF3, 0x2F00..0x2FD5,
281 0x2FF0..0x2FFB, 0x3000..0x303E, 0x3041..0x3096,
282 0x3099..0x30FF, 0x3105..0x312D, 0x3131..0x318E,
283 0x3190..0x31B7, 0x31C0..0x31E3, 0x31F0..0x321E,
284 0x3220..0x3243, 0x3250..0x32FE, 0x3300..0x4DB5,
285 0x4E00..0x9FC3, 0xA000..0xA48C, 0xA490..0xA4C6,
286 0xAC00..0xD7A3, 0xF900..0xFA2D, 0xFA30..0xFA6A,
287 0xFA70..0xFAD9, 0xFE10..0xFE19, 0xFE30..0xFE52,
288 0xFE54..0xFE66, 0xFE68..0xFE6B, 0xFF01..0xFF60,
289 0xFFE0..0xFFE6, 0x20000..0x2FFFD, 0x30000..0x3FFFD,
290 ]
291
292 AMBIGUOUS =
293 [0x00A1..0x00A1, 0x00A4..0x00A4, 0x00A7..0x00A8,
294 0x00AA..0x00AA, 0x00AD..0x00AE, 0x00B0..0x00B4,
295 0x00B6..0x00BA, 0x00BC..0x00BF, 0x00C6..0x00C6,
296 0x00D0..0x00D0, 0x00D7..0x00D8, 0x00DE..0x00E1,
297 0x00E6..0x00E6, 0x00E8..0x00EA, 0x00EC..0x00ED,
298 0x00F0..0x00F0, 0x00F2..0x00F3, 0x00F7..0x00FA,
299 0x00FC..0x00FC, 0x00FE..0x00FE, 0x0101..0x0101,
300 0x0111..0x0111, 0x0113..0x0113, 0x011B..0x011B,
301 0x0126..0x0127, 0x012B..0x012B, 0x0131..0x0133,
302 0x0138..0x0138, 0x013F..0x0142, 0x0144..0x0144,
303 0x0148..0x014B, 0x014D..0x014D, 0x0152..0x0153,
304 0x0166..0x0167, 0x016B..0x016B, 0x01CE..0x01CE,
305 0x01D0..0x01D0, 0x01D2..0x01D2, 0x01D4..0x01D4,
306 0x01D6..0x01D6, 0x01D8..0x01D8, 0x01DA..0x01DA,
307 0x01DC..0x01DC, 0x0251..0x0251, 0x0261..0x0261,
308 0x02C4..0x02C4, 0x02C7..0x02C7, 0x02C9..0x02CB,
309 0x02CD..0x02CD, 0x02D0..0x02D0, 0x02D8..0x02DB,
310 0x02DD..0x02DD, 0x02DF..0x02DF, 0x0300..0x036F,
311 0x0391..0x03A1, 0x03A3..0x03A9, 0x03B1..0x03C1,
312 0x03C3..0x03C9, 0x0401..0x0401, 0x0410..0x044F,
313 0x0451..0x0451, 0x2010..0x2010, 0x2013..0x2016,
314 0x2018..0x2019, 0x201C..0x201D, 0x2020..0x2022,
315 0x2024..0x2027, 0x2030..0x2030, 0x2032..0x2033,
316 0x2035..0x2035, 0x203B..0x203B, 0x203E..0x203E,
317 0x2074..0x2074, 0x207F..0x207F, 0x2081..0x2084,
318 0x20AC..0x20AC, 0x2103..0x2103, 0x2105..0x2105,
319 0x2109..0x2109, 0x2113..0x2113, 0x2116..0x2116,
320 0x2121..0x2122, 0x2126..0x2126, 0x212B..0x212B,
321 0x2153..0x2154, 0x215B..0x215E, 0x2160..0x216B,
322 0x2170..0x2179, 0x2190..0x2199, 0x21B8..0x21B9,
323 0x21D2..0x21D2, 0x21D4..0x21D4, 0x21E7..0x21E7,
324 0x2200..0x2200, 0x2202..0x2203, 0x2207..0x2208,
325 0x220B..0x220B, 0x220F..0x220F, 0x2211..0x2211,
326 0x2215..0x2215, 0x221A..0x221A, 0x221D..0x2220,
327 0x2223..0x2223, 0x2225..0x2225, 0x2227..0x222C,
328 0x222E..0x222E, 0x2234..0x2237, 0x223C..0x223D,
329 0x2248..0x2248, 0x224C..0x224C, 0x2252..0x2252,
330 0x2260..0x2261, 0x2264..0x2267, 0x226A..0x226B,
331 0x226E..0x226F, 0x2282..0x2283, 0x2286..0x2287,
332 0x2295..0x2295, 0x2299..0x2299, 0x22A5..0x22A5,
333 0x22BF..0x22BF, 0x2312..0x2312, 0x2460..0x24E9,
334 0x24EB..0x254B, 0x2550..0x2573, 0x2580..0x258F,
335 0x2592..0x2595, 0x25A0..0x25A1, 0x25A3..0x25A9,
336 0x25B2..0x25B3, 0x25B6..0x25B7, 0x25BC..0x25BD,
337 0x25C0..0x25C1, 0x25C6..0x25C8, 0x25CB..0x25CB,
338 0x25CE..0x25D1, 0x25E2..0x25E5, 0x25EF..0x25EF,
339 0x2605..0x2606, 0x2609..0x2609, 0x260E..0x260F,
340 0x2614..0x2615, 0x261C..0x261C, 0x261E..0x261E,
341 0x2640..0x2640, 0x2642..0x2642, 0x2660..0x2661,
342 0x2663..0x2665, 0x2667..0x266A, 0x266C..0x266D,
343 0x266F..0x266F, 0x273D..0x273D, 0x2776..0x277F,
344 0xE000..0xF8FF, 0xFE00..0xFE0F, 0xFFFD..0xFFFD,
345 0xE0100..0xE01EF, 0xF0000..0xFFFFD, 0x100000..0x10FFFD,
346 ]
347
348 def wide_character?(character)
349 binary_search_ranges(character, WIDE_CHARACTERS) or
350 binary_search_ranges(character, AMBIGUOUS)
351 end
352
353 private
354 def binary_search_ranges(character, ranges)
355 if ranges.size.zero?
356 false
357 elsif ranges.size == 1
358 ranges[0].include?(character)
359 else
360 half = ranges.size / 2
361 range = ranges[half]
362 if range.include?(character)
363 true
364 elsif character < range.begin
365 binary_search_ranges(character, ranges[0...half])
366 else
367 binary_search_ranges(character, ranges[(half + 1)..-1])
368 end
369 end
370 end
371 end
372
373 def initialize(line)
374 @line = line
375 @characters = @line.unpack("U*")
376 end
377
378 def [](*args)
379 result = @characters[*args]
380 if result.respond_to?(:pack)
381 result.pack("U*")
382 else
383 result
384 end
385 end
386
387 def each(&block)
388 @characters.each(&block)
389 end
390
391 def size
392 @characters.size
393 end
394
395 def to_s
396 @line
397 end
398
399 def compute_width(start, _end)
400 width = 0
401 start.upto(_end - 1) do |i|
402 if self.class.wide_character?(@characters[i])
403 width += 2
404 else
405 width += 1
406 end
407 end
408 width
409 end
410 end
411
412 class ReadableDiffer < Differ
413 def diff(options={})
414 @result = []
415 operations.each do |tag, from_start, from_end, to_start, to_end|
416 case tag
417 when :replace
418 diff_lines(from_start, from_end, to_start, to_end)
419 when :delete
420 tag_deleted(@from[from_start...from_end])
421 when :insert
422 tag_inserted(@to[to_start...to_end])
423 when :equal
424 tag_equal(@from[from_start...from_end])
425 else
426 raise "unknown tag: #{tag}"
427 end
428 end
429 @result
430 end
431
432 private
433 def operations
434 @operations ||= nil
435 if @operations.nil?
436 matcher = SequenceMatcher.new(@from, @to)
437 @operations = matcher.operations
438 end
439 @operations
440 end
441
442 def default_ratio
443 0.74
444 end
445
446 def cut_off_ratio
447 0.75
448 end
449
450 def tag(mark, contents)
451 contents.each do |content|
452 @result << "#{mark}#{content}"
453 end
454 end
455
456 def tag_deleted(contents)
457 tag("- ", contents)
458 end
459
460 def tag_inserted(contents)
461 tag("+ ", contents)
462 end
463
464 def tag_equal(contents)
465 tag(" ", contents)
466 end
467
468 def tag_difference(contents)
469 tag("? ", contents)
470 end
471
472 def find_diff_line_info(from_start, from_end, to_start, to_end)
473 best_ratio = default_ratio
474 from_equal_index = to_equal_index = nil
475 from_best_index = to_best_index = nil
476
477 to_start.upto(to_end - 1) do |to_index|
478 from_start.upto(from_end - 1) do |from_index|
479 if @from[from_index] == @to[to_index]
480 from_equal_index ||= from_index
481 to_equal_index ||= to_index
482 next
483 end
484
485 matcher = SequenceMatcher.new(@from[from_index], @to[to_index],
486 &method(:space_character?))
487 if matcher.ratio > best_ratio
488 best_ratio = matcher.ratio
489 from_best_index = from_index
490 to_best_index = to_index
491 end
492 end
493 end
494
495 [best_ratio,
496 from_equal_index, to_equal_index,
497 from_best_index, to_best_index]
498 end
499
500 def diff_lines(from_start, from_end, to_start, to_end)
501 info = find_diff_line_info(from_start, from_end, to_start, to_end)
502 best_ratio, from_equal_index, to_equal_index, *info = info
503 from_best_index, to_best_index = info
504 from_best_index ||= from_start
505 to_best_index ||= to_start
506
507 if best_ratio < cut_off_ratio
508 if from_equal_index.nil?
509 if to_end - to_start < from_end - from_start
510 tag_inserted(@to[to_start...to_end])
511 tag_deleted(@from[from_start...from_end])
512 else
513 tag_deleted(@from[from_start...from_end])
514 tag_inserted(@to[to_start...to_end])
515 end
516 return
517 end
518 from_best_index = from_equal_index
519 to_best_index = to_equal_index
520 best_ratio = 1.0
521 end
522
523 _diff_lines(from_start, from_best_index, to_start, to_best_index)
524 diff_line(@from[from_best_index], @to[to_best_index])
525 _diff_lines(from_best_index + 1, from_end, to_best_index + 1, to_end)
526 end
527
528 def _diff_lines(from_start, from_end, to_start, to_end)
529 if from_start < from_end
530 if to_start < to_end
531 diff_lines(from_start, from_end, to_start, to_end)
532 else
533 tag_deleted(@from[from_start...from_end])
534 end
535 else
536 tag_inserted(@to[to_start...to_end])
537 end
538 end
539
540 def line_operations(from_line, to_line)
541 if !from_line.respond_to?(:force_encoding) and $KCODE == "UTF8"
542 from_line = UTF8Line.new(from_line)
543 to_line = UTF8Line.new(to_line)
544 end
545 matcher = SequenceMatcher.new(from_line, to_line,
546 &method(:space_character?))
547 [from_line, to_line, matcher.operations]
548 end
549
550 def compute_width(line, start, _end)
551 if line.respond_to?(:encoding) and
552 Encoding.compatible?(Encoding::UTF_8, line.encoding)
553 utf8_line = line[start..._end].encode(Encoding::UTF_8)
554 width = 0
555 utf8_line.each_codepoint do |unicode_codepoint|
556 if UTF8Line.wide_character?(unicode_codepoint)
557 width += 2
558 else
559 width += 1
560 end
561 end
562 width
563 elsif line.is_a?(UTF8Line)
564 line.compute_width(start, _end)
565 else
566 _end - start
567 end
568 end
569
570 def diff_line(from_line, to_line)
571 from_tags = ""
572 to_tags = ""
573 from_line, to_line, _operations = line_operations(from_line, to_line)
574 _operations.each do |tag, from_start, from_end, to_start, to_end|
575 from_width = compute_width(from_line, from_start, from_end)
576 to_width = compute_width(to_line, to_start, to_end)
577 case tag
578 when :replace
579 from_tags << "^" * from_width
580 to_tags << "^" * to_width
581 when :delete
582 from_tags << "-" * from_width
583 when :insert
584 to_tags << "+" * to_width
585 when :equal
586 from_tags << " " * from_width
587 to_tags << " " * to_width
588 else
589 raise "unknown tag: #{tag}"
590 end
591 end
592 format_diff_point(from_line, to_line, from_tags, to_tags)
593 end
594
595 def format_diff_point(from_line, to_line, from_tags, to_tags)
596 common = [n_leading_characters(from_line, ?\t),
597 n_leading_characters(to_line, ?\t)].min
598 common = [common,
599 n_leading_characters(from_tags[0, common], " "[0])].min
600 from_tags = from_tags[common..-1].rstrip
601 to_tags = to_tags[common..-1].rstrip
602
603 tag_deleted([from_line])
604 unless from_tags.empty?
605 tag_difference(["#{"\t" * common}#{from_tags}"])
606 end
607 tag_inserted([to_line])
608 unless to_tags.empty?
609 tag_difference(["#{"\t" * common}#{to_tags}"])
610 end
611 end
612
613 def n_leading_characters(string, character)
614 n = 0
615 while string[n] == character
616 n += 1
617 end
618 n
619 end
620
621 def space_character?(character)
622 [" "[0], "\t"[0]].include?(character)
623 end
624 end
625
626 class UnifiedDiffer < Differ
627 def diff(options={})
628 groups = SequenceMatcher.new(@from, @to).grouped_operations
629 return [] if groups.empty?
630 return [] if same_content?(groups)
631
632 show_context = options[:show_context]
633 show_context = true if show_context.nil?
634 result = ["--- #{options[:from_label]}".rstrip,
635 "+++ #{options[:to_label]}".rstrip]
636 groups.each do |operations|
637 result << format_summary(operations, show_context)
638 operations.each do |args|
639 operation_tag, from_start, from_end, to_start, to_end = args
640 case operation_tag
641 when :replace
642 result.concat(tag("-", @from[from_start...from_end]))
643 result.concat(tag("+", @to[to_start...to_end]))
644 when :delete
645 result.concat(tag("-", @from[from_start...from_end]))
646 when :insert
647 result.concat(tag("+", @to[to_start...to_end]))
648 when :equal
649 result.concat(tag(" ", @from[from_start...from_end]))
650 end
651 end
652 end
653 result
654 end
655
656 private
657 def same_content?(groups)
658 return false if groups.size != 1
659 group = groups[0]
660 return false if group.size != 1
661 tag, from_start, from_end, to_start, to_end = group[0]
662
663 tag == :equal and [from_start, from_end] == [to_start, to_end]
664 end
665
666 def format_summary(operations, show_context)
667 _, first_from_start, _, first_to_start, _ = operations[0]
668 _, _, last_from_end, _, last_to_end = operations[-1]
669 summary = "@@ -%d,%d +%d,%d @@" % [first_from_start + 1,
670 last_from_end - first_from_start,
671 first_to_start + 1,
672 last_to_end - first_to_start,]
673 if show_context
674 interesting_line = find_interesting_line(first_from_start,
675 first_to_start,
676 :define_line?)
677 summary << " #{interesting_line}" if interesting_line
678 end
679 summary
680 end
681
682 def find_interesting_line(from_start, to_start, predicate)
683 from_index = from_start
684 to_index = to_start
685 while from_index >= 0 or to_index >= 0
686 [@from[from_index], @to[to_index]].each do |line|
687 return line if line and __send__(predicate, line)
688 end
689
690 from_index -= 1
691 to_index -= 1
692 end
693 nil
694 end
695
696 def define_line?(line)
697 /\A(?:[_a-zA-Z$]|\s*(?:class|module|def)\b)/ =~ line
698 end
699 end
700
701 module_function
702 def need_fold?(diff)
703 /^[-+].{79}/ =~ diff
704 end
705
706 def fold(string)
707 string.split(/\r?\n/).collect do |line|
708 line.gsub(/(.{78})/, "\\1\n")
709 end.join("\n")
710 end
711
712 def folded_readable(from, to, options={})
713 readable(fold(from), fold(to), options)
714 end
715
716 def readable(from, to, options={})
717 diff(ReadableDiffer, from, to, options)
718 end
719
720 def unified(from, to, options={})
721 diff(UnifiedDiffer, from, to, options)
722 end
723
724 def diff(differ_class, from, to, options={})
725 if from.respond_to?(:valid_encoding?) and not from.valid_encoding?
726 from = from.dup.force_encoding("ASCII-8BIT")
727 end
728 if to.respond_to?(:valid_encoding?) and not to.valid_encoding?
729 to = to.dup.force_encoding("ASCII-8BIT")
730 end
731 differ = differ_class.new(from.split(/\r?\n/), to.split(/\r?\n/))
732 lines = differ.diff(options)
733 if Object.const_defined?(:EncodingError)
734 begin
735 lines.join("\n")
736 rescue EncodingError
737 lines.collect {|line| line.force_encoding("ASCII-8BIT")}.join("\n")
738 end
739 else
740 lines.join("\n")
741 end
742 end
743 end
744 end
745 end
+0
-158
vendor/test/unit/error.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 require 'test/unit/util/backtracefilter'
7
8 module Test
9 module Unit
10
11 # Encapsulates an error in a test. Created by
12 # Test::Unit::TestCase when it rescues an exception thrown
13 # during the processing of a test.
14 class Error
15 include Util::BacktraceFilter
16
17 attr_reader :test_name, :exception
18 attr_reader :method_name
19
20 SINGLE_CHARACTER = 'E'
21 LABEL = "Error"
22
23 # Creates a new Error with the given test_name and
24 # exception.
25 def initialize(test_name, exception, options={})
26 @test_name = test_name
27 @exception = exception
28 @method_name = options[:method_name]
29 end
30
31 # Returns a single character representation of an error.
32 def single_character_display
33 SINGLE_CHARACTER
34 end
35
36 def label
37 LABEL
38 end
39
40 # Returns the message associated with the error.
41 def message
42 "#{@exception.class.name}: #{@exception.message}"
43 end
44
45 # Returns a brief version of the error description.
46 def short_display
47 "#@test_name: #{message.split("\n")[0]}"
48 end
49
50 # Returns a verbose version of the error description.
51 def long_display
52 backtrace_display = location.join("\n ")
53 "#{label}:\n#@test_name:\n#{message}\n #{backtrace_display}"
54 end
55
56 def location
57 @location ||= filter_backtrace(@exception.backtrace)
58 end
59 alias_method :backtrace, :location # Deprecated
60
61 # Overridden to return long_display.
62 def to_s
63 long_display
64 end
65
66 def critical?
67 true
68 end
69 end
70
71 module ErrorHandler
72 class << self
73 def included(base)
74 base.exception_handler(:handle_all_exception)
75 end
76 end
77
78 NOT_PASS_THROUGH_EXCEPTIONS = []
79 NOT_PASS_THROUGH_EXCEPTION_NAMES = ["Timeout::Error"]
80 PASS_THROUGH_EXCEPTIONS = [
81 NoMemoryError,
82 SignalException,
83 Interrupt,
84 SystemExit,
85 ]
86 PASS_THROUGH_EXCEPTION_NAMES = []
87 private
88 def handle_all_exception(exception)
89 return false if pass_through_exception?(exception)
90
91 problem_occurred
92 add_error(exception)
93 true
94 end
95
96 def pass_through_exception?(exception)
97 case exception
98 when *NOT_PASS_THROUGH_EXCEPTIONS
99 return false
100 end
101 case exception.class.name
102 when *NOT_PASS_THROUGH_EXCEPTION_NAMES
103 return false
104 end
105
106 case exception
107 when *PASS_THROUGH_EXCEPTIONS
108 return true
109 end
110 case exception.class.name
111 when *PASS_THROUGH_EXCEPTION_NAMES
112 return true
113 end
114
115 false
116 end
117
118 def add_error(exception)
119 error = Error.new(name, exception, :method_name => @method_name)
120 current_result.add_error(error)
121 end
122 end
123
124 module TestResultErrorSupport
125 attr_reader :errors
126
127 # Records a Test::Unit::Error.
128 def add_error(error)
129 @errors << error
130 notify_fault(error)
131 notify_changed
132 end
133
134 # Returns the number of errors this TestResult has
135 # recorded.
136 def error_count
137 @errors.size
138 end
139
140 def error_occurred?
141 not @errors.empty?
142 end
143
144 private
145 def initialize_containers
146 super
147 @errors = []
148 @summary_generators << :error_summary
149 @problem_checkers << :error_occurred?
150 end
151
152 def error_summary
153 "#{error_count} errors"
154 end
155 end
156 end
157 end
+0
-82
vendor/test/unit/exception-handler.rb less more
0 module Test
1 module Unit
2 module ExceptionHandler
3 @@exception_handlers = []
4 class << self
5 def exception_handlers
6 @@exception_handlers
7 end
8
9 def included(base)
10 base.extend(ClassMethods)
11
12 observer = Proc.new do |test_case, _, _, value, method_name|
13 if value
14 @@exception_handlers.unshift(method_name)
15 else
16 @@exception_handlers.delete(method_name)
17 end
18 end
19 base.register_attribute_observer(:exception_handler, &observer)
20 end
21 end
22
23 module ClassMethods
24 def exception_handlers
25 ExceptionHandler.exception_handlers
26 end
27
28 # @overload exception_handler(method_name)
29 # Add an exception handler method.
30 #
31 # @param method_name [Symbol]
32 # The method name that handles exception raised in tests.
33 # @return [void]
34 #
35 # @overload exception_handler(&callback)
36 # Add an exception handler.
37 #
38 # @yield [test, exception]
39 # Gives the test and the exception.
40 # @yieldparam test [Test::Unit::TestCase]
41 # The test where the exception is raised.
42 # @yieldparam exception [Exception]
43 # The exception that is raised in running the test.
44 # @yieldreturn [Boolean]
45 # Whether the handler handles the exception or not.
46 # The handler must return _true_ if the handler handles
47 # test exception, _false_ otherwise.
48 # @return [void]
49 #
50 # This is a public API for developers who extend test-unit.
51 def exception_handler(*method_name_or_handlers, &block)
52 if block_given?
53 exception_handlers.unshift(block)
54 else
55 method_name_or_handlers.each do |method_name_or_handler|
56 if method_name_or_handler.respond_to?(:call)
57 handler = method_name_or_handler
58 exception_handlers.unshift(handler)
59 else
60 method_name = method_name_or_handler
61 attribute(:exception_handler, true, {}, method_name)
62 end
63 end
64 end
65 end
66
67 def unregister_exception_handler(*method_name_or_handlers)
68 method_name_or_handlers.each do |method_name_or_handler|
69 if method_name_or_handler.respond_to?(:call)
70 handler = method_name_or_handler
71 exception_handlers.delete(handler)
72 else
73 method_name = method_name_or_handler
74 attribute(:exception_handler, false, {}, method_name)
75 end
76 end
77 end
78 end
79 end
80 end
81 end
+0
-169
vendor/test/unit/failure.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 module Test
7 module Unit
8
9 # Encapsulates a test failure. Created by Test::Unit::TestCase
10 # when an assertion fails.
11 class Failure
12 attr_reader :test_name, :location, :message
13 attr_reader :method_name, :source_location
14 attr_reader :expected, :actual, :user_message
15 attr_reader :inspected_expected, :inspected_actual
16
17 SINGLE_CHARACTER = 'F'
18 LABEL = "Failure"
19
20 # Creates a new Failure with the given location and
21 # message.
22 def initialize(test_name, location, message, options={})
23 @test_name = test_name
24 @location = location
25 @message = message
26 @method_name = options[:method_name]
27 @source_location = options[:source_location]
28 @expected = options[:expected]
29 @actual = options[:actual]
30 @inspected_expected = options[:inspected_expected]
31 @inspected_actual = options[:inspected_actual]
32 @user_message = options[:user_message]
33 end
34
35 # Returns a single character representation of a failure.
36 def single_character_display
37 SINGLE_CHARACTER
38 end
39
40 def label
41 LABEL
42 end
43
44 # Returns a brief version of the error description.
45 def short_display
46 "#@test_name: #{@message.split("\n")[0]}"
47 end
48
49 # Returns a verbose version of the error description.
50 def long_display
51 if location.size == 1
52 location_display = location[0].sub(/\A(.+:\d+).*/, ' [\\1]')
53 else
54 location_display = "\n [#{location.join("\n ")}]"
55 end
56 "#{label}:\n#@test_name#{location_display}:\n#@message"
57 end
58
59 # Overridden to return long_display.
60 def to_s
61 long_display
62 end
63
64 def critical?
65 true
66 end
67
68 def diff
69 @diff ||= compute_diff
70 end
71
72 private
73 def compute_diff
74 Assertions::AssertionMessage.delayed_diff(@expected, @actual).inspect
75 end
76 end
77
78 module FailureHandler
79 class << self
80 def included(base)
81 base.exception_handler(:handle_assertion_failed_error)
82 end
83 end
84
85 # Report a failure.
86 #
87 # This is a public API for developers who extend test-unit.
88 #
89 # @param message [String] The description about the failure.
90 # @param backtrace [Array<String>] The backtrace for the failure.
91 # @option options [Object] :expected
92 # The expected value of the assertion.
93 # @option options [Object] :actual
94 # The actual value of the assertion.
95 # @option options [String] :inspected_expected
96 # The inspected expected value of the assertion.
97 # It is used for diff between expected and actual of the failure.
98 # @option options [String] :inspected_actual
99 # The inspected actual value of the assertion.
100 # It is used for diff between expected and actual of the failure.
101 # @option options [String] :user_message
102 # The message of the assertion from user.
103 # @option options [String] :method_name (@method_name)
104 # The method name of the test.
105 # @option options [Array<String, Integer>] :source_location
106 # The location where the test is defined. It is the same
107 # format as Proc#source_location. That is, it's an array of
108 # path and and line number where the test definition is
109 # started.
110 # @return [void]
111 def add_failure(message, backtrace, options={})
112 default_options = {
113 :method_name => @method_name,
114 :source_location => self[:source_location],
115 }
116 failure = Failure.new(name, filter_backtrace(backtrace), message,
117 default_options.merge(options))
118 current_result.add_failure(failure)
119 end
120
121 private
122 def handle_assertion_failed_error(exception)
123 return false unless exception.is_a?(AssertionFailedError)
124 problem_occurred
125 add_failure(exception.message, exception.backtrace,
126 :expected => exception.expected,
127 :actual => exception.actual,
128 :inspected_expected => exception.inspected_expected,
129 :inspected_actual => exception.inspected_actual,
130 :user_message => exception.user_message)
131 true
132 end
133 end
134
135 module TestResultFailureSupport
136 attr_reader :failures
137
138 # Records a Test::Unit::Failure.
139 def add_failure(failure)
140 @failures << failure
141 notify_fault(failure)
142 notify_changed
143 end
144
145 # Returns the number of failures this TestResult has
146 # recorded.
147 def failure_count
148 @failures.size
149 end
150
151 def failure_occurred?
152 not @failures.empty?
153 end
154
155 private
156 def initialize_containers
157 super
158 @failures = []
159 @summary_generators << :failure_summary
160 @problem_checkers << :failure_occurred?
161 end
162
163 def failure_summary
164 "#{failure_count} failures"
165 end
166 end
167 end
168 end
+0
-100
vendor/test/unit/fault-location-detector.rb less more
0 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
1 #
2 # License: Ruby's
3
4 require "English"
5
6 module Test
7 module Unit
8 class FaultLocationDetector
9 def initialize(fault, code_snippet_fetcher)
10 @fault = fault
11 @code_snippet_fetcher = code_snippet_fetcher
12 extract_fault_information
13 end
14
15 def split_backtrace_entry(entry)
16 match_data = /\A(.+):(\d+)(?::(.*))?\z/.match(entry)
17 return nil if match_data.nil?
18 file, line_number, context = match_data.to_a[1..-1]
19 line_number = line_number.to_i
20 if /\Ain `(.+?)'/ =~ context
21 method_name = $1
22 if /\Ablock (?:\(.+?\) )?in / =~ method_name
23 method_name = $POSTMATCH
24 end
25 else
26 method_name = nil
27 end
28 [file, line_number, context, method_name]
29 end
30
31 def target?(backtrace_entry)
32 file, line_number, context, method_name =
33 split_backtrace_entry(backtrace_entry)
34 _ = context
35 return false if file.nil?
36
37 if @fault_source_location
38 target_source_location?(file, line_number, method_name)
39 elsif @fault_method_name
40 target_method?(method_name)
41 else
42 true
43 end
44 end
45
46 private
47 def target_source_location?(file, line_number, method_name)
48 fault_file, fault_line_number = @fault_source_location
49 return false unless file.end_with?(fault_file)
50
51 return false if line_number < fault_line_number
52
53 lines = @code_snippet_fetcher.source(file)
54 return false if lines.nil?
55
56 base_indent_level = nil
57 fault_line_number.step(lines.size) do |current_line_number|
58 line = lines[current_line_number - 1]
59 current_indent_level = guess_indent_level(line)
60 base_indent_level ||= current_indent_level
61 return true if current_line_number == line_number
62
63 if current_line_number == fault_line_number
64 break if /(?:\send|\})\s*$/ =~ line
65 else
66 break if current_indent_level == base_indent_level
67 end
68 end
69 false
70 end
71
72 def target_method?(method_name)
73 @fault_method_name == method_name
74 end
75
76 def guess_indent_level(line)
77 if /\A(\s*)/ =~ line
78 $1.sub(/\t/, " " * 8).count(" ")
79 else
80 0
81 end
82 end
83
84 def extract_fault_information
85 if @fault.respond_to?(:source_location)
86 @fault_source_location = @fault.source_location
87 else
88 @fault_source_location = nil
89 end
90
91 if @fault.respond_to?(:method_name)
92 @fault_method_name = @fault.method_name
93 else
94 @fault_method_name = nil
95 end
96 end
97 end
98 end
99 end
+0
-254
vendor/test/unit/fixture.rb less more
0 module Test
1 module Unit
2 module Fixture
3 class << self
4 def included(base)
5 base.extend(ClassMethods)
6
7 [:setup, :cleanup, :teardown].each do |type|
8 observer = lambda do |test_case, _, _, value, callback|
9 if value.nil?
10 test_case.fixture[type].unregister(callback)
11 else
12 test_case.fixture[type].register(callback, value)
13 end
14 end
15 base.register_attribute_observer(type, &observer)
16 end
17 end
18 end
19
20 class Fixture
21 attr_reader :setup
22 attr_reader :cleanup
23 attr_reader :teardown
24 def initialize(test_case)
25 @test_case = test_case
26 @setup = HookPoint.new(:after => :append)
27 @cleanup = HookPoint.new(:before => :prepend)
28 @teardown = HookPoint.new(:before => :prepend)
29 end
30
31 def [](type)
32 case type
33 when :setup
34 @setup
35 when :cleanup
36 @cleanup
37 when :teardown
38 @teardown
39 end
40 end
41
42 def before_callbacks(type)
43 prepend_callbacks = []
44 append_callbacks = []
45 target_test_cases.each do |ancestor|
46 prepend_callbacks << ancestor.fixture[type].before_prepend_callbacks
47 append_callbacks << ancestor.fixture[type].before_append_callbacks
48 end
49
50 merge_callbacks(prepend_callbacks, append_callbacks)
51 end
52
53 def after_callbacks(type)
54 prepend_callbacks = []
55 append_callbacks = []
56 target_test_cases.each do |ancestor|
57 prepend_callbacks << ancestor.fixture[type].after_prepend_callbacks
58 append_callbacks << ancestor.fixture[type].after_append_callbacks
59 end
60
61 merge_callbacks(prepend_callbacks, append_callbacks)
62 end
63
64 private
65 def target_test_cases
66 @cached_target_test_cases ||= collect_target_test_cases
67 end
68
69 def collect_target_test_cases
70 ancestors = @test_case.ancestors
71 base_index = ancestors.index(::Test::Unit::Fixture)
72 interested_ancestors = ancestors[0, base_index].find_all do |ancestor|
73 ancestor.is_a?(Class)
74 end
75 interested_ancestors.reverse
76 end
77
78 def merge_callbacks(prepend_callbacks, append_callbacks)
79 all_callbacks = []
80 prepend_callbacks.reverse_each do |callbacks|
81 all_callbacks.concat(callbacks)
82 end
83 append_callbacks.each do |callbacks|
84 all_callbacks.concat(callbacks)
85 end
86 all_callbacks
87 end
88 end
89
90 class HookPoint
91 def initialize(default_options)
92 @default_options = default_options
93 @before_prepend_callbacks = []
94 @before_append_callbacks = []
95 @after_prepend_callbacks = []
96 @after_append_callbacks = []
97 @unregistered_callbacks = []
98 end
99
100 def register(method_name_or_callback, options=nil)
101 options ||= {}
102 unless valid_register_options?(options)
103 message = "must be {:before => :prepend}, " +
104 "{:before => :append}, {:after => :prepend} or " +
105 "{:after => :append}: #{options.inspect}"
106 raise ArgumentError, message
107 end
108
109 if options.empty?
110 options = @default_options
111 end
112 before_how = options[:before]
113 after_how = options[:after]
114 add_callback(method_name_or_callback, before_how, after_how)
115 end
116
117 def unregister(method_name_or_callback)
118 @unregistered_callbacks << method_name_or_callback
119 end
120
121 def before_prepend_callbacks
122 @before_prepend_callbacks - @unregistered_callbacks
123 end
124
125 def before_append_callbacks
126 @before_append_callbacks - @unregistered_callbacks
127 end
128
129 def after_prepend_callbacks
130 @after_prepend_callbacks - @unregistered_callbacks
131 end
132
133 def after_append_callbacks
134 @after_append_callbacks - @unregistered_callbacks
135 end
136
137 private
138 def valid_register_options?(options)
139 return true if options.empty?
140 return false if options.size > 1
141
142 key = options.keys.first
143 [:before, :after].include?(key) and
144 [:prepend, :append].include?(options[key])
145 end
146
147 def add_callback(method_name_or_callback, before_how, after_how)
148 case before_how
149 when :prepend
150 @before_prepend_callbacks =
151 [method_name_or_callback] | @before_prepend_callbacks
152 when :append
153 @before_append_callbacks |= [method_name_or_callback]
154 else
155 case after_how
156 when :prepend
157 @after_prepend_callbacks =
158 [method_name_or_callback] | @after_prepend_callbacks
159 when :append
160 @after_append_callbacks |= [method_name_or_callback]
161 end
162 end
163 end
164 end
165
166 module ClassMethods
167 def fixture
168 @fixture ||= Fixture.new(self)
169 end
170
171 def setup(*method_names, &callback)
172 register_fixture(:setup, *method_names, &callback)
173 end
174
175 def unregister_setup(*method_names_or_callbacks)
176 unregister_fixture(:setup, *method_names_or_callbacks)
177 end
178
179 def cleanup(*method_names, &callback)
180 register_fixture(:cleanup, *method_names, &callback)
181 end
182
183 def unregister_cleanup(*method_names_or_callbacks)
184 unregister_fixture(:cleanup, *method_names_or_callbacks)
185 end
186
187 def teardown(*method_names, &callback)
188 register_fixture(:teardown, *method_names, &callback)
189 end
190
191 def unregister_teardown(*method_names_or_callbacks)
192 unregister_fixture(:teardown, *method_names_or_callbacks)
193 end
194
195 private
196 def register_fixture(fixture, *method_names, &callback)
197 options = {}
198 options = method_names.pop if method_names.last.is_a?(Hash)
199 callbacks = method_names
200 callbacks << callback if callback
201 attribute(fixture, options, *callbacks)
202 end
203
204 def unregister_fixture(fixture, *method_names_or_callbacks)
205 attribute(fixture, nil, *method_names_or_callbacks)
206 end
207 end
208
209 private
210 def run_fixture(type, options={})
211 [
212 self.class.fixture.before_callbacks(type),
213 type,
214 self.class.fixture.after_callbacks(type),
215 ].flatten.each do |method_name_or_callback|
216 run_fixture_callback(method_name_or_callback, options)
217 end
218 end
219
220 def run_fixture_callback(method_name_or_callback, options)
221 if method_name_or_callback.respond_to?(:call)
222 callback = lambda do
223 instance_eval(&method_name_or_callback)
224 end
225 else
226 return unless respond_to?(method_name_or_callback, true)
227 callback = lambda do
228 __send__(method_name_or_callback)
229 end
230 end
231
232 begin
233 callback.call
234 rescue Exception
235 raise unless options[:handle_exception]
236 raise unless handle_exception($!)
237 end
238 end
239
240 def run_setup
241 run_fixture(:setup)
242 end
243
244 def run_cleanup
245 run_fixture(:cleanup)
246 end
247
248 def run_teardown
249 run_fixture(:teardown, :handle_exception => true)
250 end
251 end
252 end
253 end
+0
-136
vendor/test/unit/notification.rb less more
0 require 'test/unit/util/backtracefilter'
1
2 module Test
3 module Unit
4 class Notification
5 include Util::BacktraceFilter
6 attr_reader :test_name, :location, :message
7 attr_reader :method_name
8
9 SINGLE_CHARACTER = 'N'
10 LABEL = "Notification"
11
12 # Creates a new Notification with the given location and
13 # message.
14 def initialize(test_name, location, message, options={})
15 @test_name = test_name
16 @location = location
17 @message = message
18 @method_name = options[:method_name]
19 end
20
21 # Returns a single character representation of a notification.
22 def single_character_display
23 SINGLE_CHARACTER
24 end
25
26 def label
27 LABEL
28 end
29
30 # Returns a brief version of the error description.
31 def short_display
32 "#{@test_name}: #{@message.split("\n")[0]}"
33 end
34
35 # Returns a verbose version of the error description.
36 def long_display
37 backtrace = filter_backtrace(location).join("\n")
38 "#{label}: #{@message}\n#{@test_name}\n#{backtrace}"
39 end
40
41 # Overridden to return long_display.
42 def to_s
43 long_display
44 end
45
46 def critical?
47 false
48 end
49 end
50
51 class NotifiedError < StandardError
52 end
53
54
55 module TestCaseNotificationSupport
56 class << self
57 def included(base)
58 base.class_eval do
59 include NotificationHandler
60 end
61 end
62 end
63
64 # Notify some information.
65 #
66 # Example:
67 # def test_notification
68 # notify("I'm here!")
69 # # Reached here
70 # notify("Special!") if special_case?
71 # # Reached here too
72 # end
73 #
74 # options:
75 # :backtrace override backtrace.
76 def notify(message, options={}, &block)
77 backtrace = filter_backtrace(options[:backtrace] || caller)
78 notification = Notification.new(name, backtrace, message,
79 :method_name => @method_name)
80 add_notification(notification)
81 end
82
83 private
84 def add_notification(notification)
85 current_result.add_notification(notification)
86 end
87 end
88
89 module NotificationHandler
90 class << self
91 def included(base)
92 base.exception_handler(:handle_notified_error)
93 end
94 end
95
96 private
97 def handle_notified_error(exception)
98 return false unless exception.is_a?(NotifiedError)
99 notification = Notification.new(name,
100 filter_backtrace(exception.backtrace),
101 exception.message)
102 add_notification(notification)
103 true
104 end
105 end
106
107 module TestResultNotificationSupport
108 attr_reader :notifications
109
110 # Records a Test::Unit::Notification.
111 def add_notification(notification)
112 @notifications << notification
113 notify_fault(notification)
114 notify_changed
115 end
116
117 # Returns the number of notifications this TestResult has
118 # recorded.
119 def notification_count
120 @notifications.size
121 end
122
123 private
124 def initialize_containers
125 super
126 @notifications = []
127 @summary_generators << :notification_summary
128 end
129
130 def notification_summary
131 "#{notification_count} notifications"
132 end
133 end
134 end
135 end
+0
-195
vendor/test/unit/omission.rb less more
0 require 'test/unit/util/backtracefilter'
1
2 module Test
3 module Unit
4 class Omission
5 include Util::BacktraceFilter
6 attr_reader :test_name, :location, :message
7 attr_reader :method_name
8
9 SINGLE_CHARACTER = 'O'
10 LABEL = "Omission"
11
12 # Creates a new Omission with the given location and
13 # message.
14 def initialize(test_name, location, message, options={})
15 @test_name = test_name
16 @location = location
17 @message = message
18 @method_name = options[:method_name]
19 end
20
21 # Returns a single character representation of a omission.
22 def single_character_display
23 SINGLE_CHARACTER
24 end
25
26 def label
27 LABEL
28 end
29
30 # Returns a brief version of the error description.
31 def short_display
32 "#{@test_name}: #{@message.split("\n")[0]}"
33 end
34
35 # Returns a verbose version of the error description.
36 def long_display
37 backtrace = filter_backtrace(location).join("\n")
38 "#{label}: #{@message}\n#{@test_name}\n#{backtrace}"
39 end
40
41 # Overridden to return long_display.
42 def to_s
43 long_display
44 end
45
46 def critical?
47 true
48 end
49 end
50
51 class OmittedError < StandardError
52 end
53
54
55 module TestCaseOmissionSupport
56 class << self
57 def included(base)
58 base.class_eval do
59 include OmissionHandler
60 end
61 end
62 end
63
64 # Omit the test or part of the test.
65 #
66 # Example:
67 # def test_omission
68 # omit
69 # # Not reached here
70 # end
71 #
72 # def test_omission_with_here
73 # omit do
74 # # Not ran here
75 # end
76 # # Reached here
77 # end
78 def omit(message=nil, &block)
79 message ||= "omitted."
80 if block_given?
81 omission = Omission.new(name, filter_backtrace(caller), message,
82 :method_name => @method_name)
83 add_omission(omission)
84 else
85 raise OmittedError.new(message)
86 end
87 end
88
89 # Omit the test or part of the test if _condition_ is
90 # true.
91 #
92 # Example:
93 # def test_omission
94 # omit_if("".empty?)
95 # # Not reached here
96 # end
97 #
98 # def test_omission_with_here
99 # omit_if(true) do
100 # # Not ran here
101 # end
102 # omit_if(false) do
103 # # Reached here
104 # end
105 # # Reached here too
106 # end
107 def omit_if(condition, *args, &block)
108 if condition
109 omit(*args, &block)
110 else
111 block.call if block
112 end
113 end
114
115 # Omit the test or part of the test if _condition_ is
116 # not true.
117 #
118 # Example:
119 # def test_omission
120 # omit_unless("string".empty?)
121 # # Not reached here
122 # end
123 #
124 # def test_omission_with_here
125 # omit_unless(true) do
126 # # Reached here
127 # end
128 # omit_unless(false) do
129 # # Not ran here
130 # end
131 # # Reached here too
132 # end
133 def omit_unless(condition, *args, &block)
134 if condition
135 block.call if block
136 else
137 omit(*args, &block)
138 end
139 end
140
141 private
142 def add_omission(omission)
143 current_result.add_omission(omission)
144 end
145 end
146
147 module OmissionHandler
148 class << self
149 def included(base)
150 base.exception_handler(:handle_omitted_error)
151 end
152 end
153
154 private
155 def handle_omitted_error(exception)
156 return false unless exception.is_a?(OmittedError)
157 omission = Omission.new(name,
158 filter_backtrace(exception.backtrace),
159 exception.message,
160 :method_name => @method_name)
161 add_omission(omission)
162 true
163 end
164 end
165
166 module TestResultOmissionSupport
167 attr_reader :omissions
168
169 # Records a Test::Unit::Omission.
170 def add_omission(omission)
171 @omissions << omission
172 notify_fault(omission)
173 notify_changed
174 end
175
176 # Returns the number of omissions this TestResult has
177 # recorded.
178 def omission_count
179 @omissions.size
180 end
181
182 private
183 def initialize_containers
184 super
185 @omissions = []
186 @summary_generators << :omission_summary
187 end
188
189 def omission_summary
190 "#{omission_count} omissions"
191 end
192 end
193 end
194 end
+0
-154
vendor/test/unit/pending.rb less more
0 require 'test/unit/util/backtracefilter'
1
2 module Test
3 module Unit
4 class Pending
5 include Util::BacktraceFilter
6 attr_reader :test_name, :location, :message
7 attr_reader :method_name
8
9 SINGLE_CHARACTER = 'P'
10 LABEL = "Pending"
11
12 # Creates a new Pending with the given location and
13 # message.
14 def initialize(test_name, location, message, options={})
15 @test_name = test_name
16 @location = location
17 @message = message
18 @method_name = options[:method_name]
19 end
20
21 # Returns a single character representation of a pending.
22 def single_character_display
23 SINGLE_CHARACTER
24 end
25
26 def label
27 LABEL
28 end
29
30 # Returns a brief version of the error description.
31 def short_display
32 "#{@test_name}: #{@message.split("\n")[0]}"
33 end
34
35 # Returns a verbose version of the error description.
36 def long_display
37 backtrace = filter_backtrace(location).join("\n")
38 "#{label}: #{@message}\n#{@test_name}\n#{backtrace}"
39 end
40
41 # Overridden to return long_display.
42 def to_s
43 long_display
44 end
45
46 def critical?
47 true
48 end
49 end
50
51 class PendedError < StandardError
52 end
53
54
55 module TestCasePendingSupport
56 class << self
57 def included(base)
58 base.class_eval do
59 include PendingHandler
60 end
61 end
62 end
63
64 # Marks the test or part of the test is pending.
65 #
66 # Example:
67 # def test_pending
68 # pend
69 # # Not reached here
70 # end
71 #
72 # def test_pending_with_here
73 # pend do
74 # # Ran here
75 # # Fails if the block doesn't raise any error.
76 # # Because it means the block is passed unexpectedly.
77 # end
78 # # Reached here
79 # end
80 def pend(message=nil, &block)
81 message ||= "pended."
82 if block_given?
83 pending = nil
84 begin
85 yield
86 rescue Exception
87 pending = Pending.new(name, filter_backtrace(caller), message,
88 :method_name => @method_name)
89 add_pending(pending)
90 end
91 unless pending
92 flunk("Pending block should not be passed: #{message}")
93 end
94 else
95 raise PendedError.new(message)
96 end
97 end
98
99 private
100 def add_pending(pending)
101 problem_occurred
102 current_result.add_pending(pending)
103 end
104 end
105
106 module PendingHandler
107 class << self
108 def included(base)
109 base.exception_handler(:handle_pended_error)
110 end
111 end
112
113 private
114 def handle_pended_error(exception)
115 return false unless exception.is_a?(PendedError)
116 pending = Pending.new(name,
117 filter_backtrace(exception.backtrace),
118 exception.message,
119 :method_name => @method_name)
120 add_pending(pending)
121 true
122 end
123 end
124
125 module TestResultPendingSupport
126 attr_reader :pendings
127
128 # Records a Test::Unit::Pending.
129 def add_pending(pending)
130 @pendings << pending
131 notify_fault(pending)
132 notify_changed
133 end
134
135 # Returns the number of pendings this TestResult has
136 # recorded.
137 def pending_count
138 @pendings.size
139 end
140
141 private
142 def initialize_containers
143 super
144 @pendings = []
145 @summary_generators << :pending_summary
146 end
147
148 def pending_summary
149 "#{pending_count} pendings"
150 end
151 end
152 end
153 end
+0
-192
vendor/test/unit/priority.rb less more
0 require "fileutils"
1
2 module Test
3 module Unit
4 module Priority
5 class << self
6 def included(base)
7 base.extend(ClassMethods)
8
9 base.class_eval do
10 setup :priority_setup, :before => :prepend
11 teardown :priority_teardown, :after => :append
12 end
13 end
14
15 @@enabled = false
16 def enabled?
17 @@enabled
18 end
19
20 def enable
21 require "fileutils"
22 require "tmpdir"
23 @@enabled = true
24 end
25
26 def disable
27 @@enabled = false
28 end
29
30 @@default = :normal
31 def default
32 @@default || :normal
33 end
34
35 def default=(default)
36 @@default = default
37 end
38
39 def available_values
40 Checker.available_priorities
41 end
42 end
43
44 class Checker
45 class << self
46 def have_priority?(name)
47 singleton_class = (class << self; self; end)
48 singleton_class.method_defined?(priority_check_method_name(name))
49 end
50
51 def need_to_run?(test)
52 priority = test[:priority] || Priority.default
53 if have_priority?(priority)
54 __send__(priority_check_method_name(priority), test)
55 else
56 true
57 end
58 end
59
60 def available_priorities
61 methods(false).collect do |name|
62 /\Arun_priority_(.+)\?\z/ =~ name.to_s
63 $1
64 end.compact
65 end
66
67 def run_priority_must?(test)
68 true
69 end
70
71 def run_priority_important?(test)
72 rand > 0.1
73 end
74
75 def run_priority_high?(test)
76 rand > 0.3
77 end
78
79 def run_priority_normal?(test)
80 rand > 0.5
81 end
82
83 def run_priority_low?(test)
84 rand > 0.75
85 end
86
87 def run_priority_never?(test)
88 false
89 end
90
91 private
92 def priority_check_method_name(priority_name)
93 "run_priority_#{priority_name}?"
94 end
95 end
96
97 attr_reader :test
98 def initialize(test)
99 @test = test
100 end
101
102 def setup
103 FileUtils.rm_f(passed_file)
104 end
105
106 def teardown
107 if @test.__send__(:passed?)
108 FileUtils.touch(passed_file)
109 else
110 FileUtils.rm_f(passed_file)
111 end
112 end
113
114 def need_to_run?
115 !previous_test_success? or self.class.need_to_run?(@test)
116 end
117
118 private
119 def previous_test_success?
120 File.exist?(passed_file)
121 end
122
123 def result_dir
124 components = [
125 ".test-result",
126 escape_class_name(@test.class.name || "AnonymousTestCase"),
127 escaped_method_name,
128 ]
129 parent_directories = [File.dirname($0), Dir.pwd]
130 if Process.respond_to?(:uid)
131 parent_directories << File.join(Dir.tmpdir, Process.uid.to_s)
132 end
133 parent_directories.each do |parent_directory|
134 dir = File.expand_path(File.join(parent_directory, *components))
135 begin
136 FileUtils.mkdir_p(dir)
137 return dir
138 rescue Errno::EACCES
139 end
140 end
141
142 raise Errno::EACCES, parent_directories.join(", ")
143 end
144
145 def passed_file
146 File.join(result_dir, "passed")
147 end
148
149 def escape_class_name(class_name)
150 class_name.gsub(/(?:[: \\\/])/, "_")
151 end
152
153 def escaped_method_name
154 @test.method_name.to_s.gsub(/(?:[: ]|[!?=]$)/) do |matched|
155 case matched
156 when ":"
157 "_colon_"
158 when " "
159 "_"
160 when "!"
161 ".destructive"
162 when "?"
163 ".predicate"
164 when "="
165 ".equal"
166 end
167 end
168 end
169 end
170
171 module ClassMethods
172 def priority(name, *tests)
173 unless Checker.have_priority?(name)
174 raise ArgumentError, "unknown priority: #{name}"
175 end
176 attribute(:priority, name, {:keep => true}, *tests)
177 end
178 end
179
180 def priority_setup
181 return unless Priority.enabled?
182 Checker.new(self).setup
183 end
184
185 def priority_teardown
186 return unless Priority.enabled?
187 Checker.new(self).teardown
188 end
189 end
190 end
191 end
+0
-59
vendor/test/unit/runner/console.rb less more
0 module Test
1 module Unit
2 AutoRunner.register_runner(:console) do |auto_runner|
3 require 'test/unit/ui/console/testrunner'
4 Test::Unit::UI::Console::TestRunner
5 end
6
7 AutoRunner.setup_option do |auto_runner, opts|
8 require 'test/unit/ui/console/outputlevel'
9
10 output_levels = [
11 ["silent", UI::Console::OutputLevel::SILENT],
12 ["progress", UI::Console::OutputLevel::PROGRESS_ONLY],
13 ["important-only", UI::Console::OutputLevel::IMPORTANT_FAULTS_ONLY],
14 ["normal", UI::Console::OutputLevel::NORMAL],
15 ["verbose", UI::Console::OutputLevel::VERBOSE],
16 ]
17 opts.on('-v', '--verbose=[LEVEL]', output_levels,
18 "Set the output level (default is verbose).",
19 "(#{auto_runner.keyword_display(output_levels)})") do |level|
20 level ||= output_levels.assoc("verbose")[1]
21 auto_runner.runner_options[:output_level] = level
22 end
23
24 use_color_options = [
25 [:auto, :auto],
26 ["-", false],
27 ["no", false],
28 ["false", false],
29 ["+", true],
30 ["yes", true],
31 ["true", true],
32 ]
33 opts.on("--[no-]use-color=[auto]", use_color_options,
34 "Uses color output",
35 "(default is auto)") do |use_color|
36 case use_color
37 when nil
38 use_color = true
39 when :auto
40 use_color = nil
41 end
42 auto_runner.runner_options[:use_color] = use_color
43 end
44
45 opts.on("--progress-row-max=MAX", Integer,
46 "Uses MAX as max terminal width for progress mark",
47 "(default is auto)") do |max|
48 auto_runner.runner_options[:progress_row_max] = max
49 end
50
51 opts.on("--no-show-detail-immediately",
52 "Shows not passed test details immediately.",
53 "(default is yes)") do |boolean|
54 auto_runner.runner_options[:show_detail_immediately] = boolean
55 end
56 end
57 end
58 end
+0
-8
vendor/test/unit/runner/emacs.rb less more
0 module Test
1 module Unit
2 AutoRunner.register_runner(:emacs) do |auto_runner|
3 require 'test/unit/ui/emacs/testrunner'
4 Test::Unit::UI::Emacs::TestRunner
5 end
6 end
7 end
+0
-15
vendor/test/unit/runner/xml.rb less more
0 module Test
1 module Unit
2 AutoRunner.register_runner(:xml) do |auto_runner|
3 require 'test/unit/ui/xml/testrunner'
4 Test::Unit::UI::XML::TestRunner
5 end
6
7 AutoRunner.setup_option do |auto_runner, opts|
8 opts.on("--output-file-descriptor=FD", Integer,
9 "Outputs to file descriptor FD") do |fd|
10 auto_runner.runner_options[:output_file_descriptor] = fd
11 end
12 end
13 end
14 end
+0
-82
vendor/test/unit/test-suite-creator.rb less more
0 #--
1 #
2 # Author:: Kouhei Sutou
3 # Copyright::
4 # * Copyright (c) 2011 Kouhei Sutou <tt><kou@clear-code.com></tt>
5 # License:: Ruby license.
6
7 module Test
8 module Unit
9 class TestSuiteCreator # :nodoc:
10 def initialize(test_case)
11 @test_case = test_case
12 end
13
14 def create
15 suite = TestSuite.new(@test_case.name, @test_case)
16 collect_test_names.each do |test_name|
17 data_sets = @test_case.find_attribute(test_name, :data)
18 if data_sets
19 data_sets.each do |data_set|
20 data_set = data_set.call if data_set.respond_to?(:call)
21 data_set.each do |label, data|
22 append_test(suite, test_name) do |test|
23 test.assign_test_data(label, data)
24 end
25 end
26 end
27 else
28 append_test(suite, test_name)
29 end
30 end
31 append_test(suite, "default_test") if suite.empty?
32 suite
33 end
34
35 private
36 def append_test(suite, test_name)
37 test = @test_case.new(test_name)
38 yield(test) if block_given?
39 suite << test if test.valid?
40 end
41
42 def collect_test_names
43 methods = @test_case.public_instance_methods(true)
44 super_test_case = @test_case.superclass
45 methods -= super_test_case.public_instance_methods(true)
46 methods |= @test_case.public_instance_methods(false)
47 method_names = methods.collect(&:to_s)
48 test_names = method_names.find_all do |method_name|
49 /\Atest./ =~ method_name or
50 @test_case.find_attribute(method_name, :test)
51 end
52 __send__("sort_test_names_in_#{@test_case.test_order}_order", test_names)
53 end
54
55 def sort_test_names_in_alphabetic_order(test_names)
56 test_names.sort
57 end
58
59 def sort_test_names_in_random_order(test_names)
60 test_names.sort_by {rand(test_names.size)}
61 end
62
63 def sort_test_names_in_defined_order(test_names)
64 added_method_names = @test_case.added_method_names
65 test_names.sort do |test1, test2|
66 test1_defined_order = added_method_names.index(test1)
67 test2_defined_order = added_method_names.index(test2)
68 if test1_defined_order and test2_defined_order
69 test1_defined_order <=> test2_defined_order
70 elsif test1_defined_order
71 1
72 elsif test2_defined_order
73 -1
74 else
75 test1 <=> test2
76 end
77 end
78 end
79 end
80 end
81 end
+0
-785
vendor/test/unit/testcase.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright::
4 # * Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
5 # * Copyright (c) 2008-2012 Kouhei Sutou <tt><kou@clear-code.com></tt>
6 # License:: Ruby license.
7
8 require 'test/unit/attribute'
9 require 'test/unit/fixture'
10 require 'test/unit/exception-handler'
11 require 'test/unit/assertions'
12 require 'test/unit/failure'
13 require 'test/unit/error'
14 require 'test/unit/pending'
15 require 'test/unit/omission'
16 require 'test/unit/notification'
17 require 'test/unit/priority'
18 require 'test/unit/data'
19 require 'test/unit/testsuite'
20 require 'test/unit/test-suite-creator'
21 require 'test/unit/assertion-failed-error'
22 require 'test/unit/util/backtracefilter'
23 require 'test/unit/util/output'
24 require 'test/unit/util/method-owner-finder'
25
26 module Test
27 module Unit
28
29 # Ties everything together. If you subclass and add your own
30 # test methods, it takes care of making them into tests and
31 # wrapping those tests into a suite. It also does the
32 # nitty-gritty of actually running an individual test and
33 # collecting its results into a Test::Unit::TestResult object.
34 #
35 # You can run two hooks before/after a TestCase run.
36 #
37 # Example:
38 # class TestMyClass < Test::Unit::TestCase
39 # class << self
40 # def startup
41 # ...
42 # end
43 #
44 # def shutdown
45 # ...
46 # end
47 # end
48 #
49 # def setup
50 # ...
51 # end
52 #
53 # def cleanup
54 # ...
55 # end
56 #
57 # def teardown
58 # ...
59 # end
60 #
61 # def test_my_method1
62 # ...
63 # end
64 #
65 # def test_my_method2
66 # ...
67 # end
68 # end
69 #
70 # Here is a call order:
71 # * startup
72 # * setup
73 # * test_my_method1
74 # * cleanup
75 # * teardown
76 # * setup
77 # * test_my_method2
78 # * cleanup
79 # * teardown
80 # * shutdown
81 class TestCase
82 include Attribute
83 include Fixture
84 include ExceptionHandler
85 include ErrorHandler
86 include FailureHandler
87 include TestCasePendingSupport
88 include TestCaseOmissionSupport
89 include TestCaseNotificationSupport
90 include Priority
91 include Data
92 include Assertions
93 include Util::BacktraceFilter
94 include Util::Output
95
96 STARTED = name + "::STARTED" # :nodoc:
97 FINISHED = name + "::FINISHED" # :nodoc:
98 STARTED_OBJECT = name + "::STARTED::OBJECT" # :nodoc:
99 FINISHED_OBJECT = name + "::FINISHED::OBJECT" # :nodoc:
100
101 DESCENDANTS = [] # :nodoc:
102 AVAILABLE_ORDERS = [:alphabetic, :random, :defined] # :nodoc:
103
104 class << self
105 def inherited(sub_class) # :nodoc:
106 require "test/unit"
107 DESCENDANTS << sub_class
108 super
109 end
110
111 @@added_method_names = {}
112 def method_added(name) # :nodoc:
113 super
114 added_method_names = (@@added_method_names[self] ||= {})
115 stringified_name = name.to_s
116 if added_method_names.key?(stringified_name)
117 attribute(:redefined, {:backtrace => caller}, {}, stringified_name)
118 end
119 source_location = find_attribute(stringified_name, :source_location)
120 if source_location
121 path, line = source_location
122 else
123 path, line, = caller[0].split(/:(\d+)/,2)
124 line = line.to_i if line
125 end
126 method_locations << {
127 :method_name => stringified_name,
128 :path => path,
129 :line => line,
130 }
131 added_method_names[stringified_name] = true
132 end
133
134 def added_method_names # :nodoc:
135 (@@added_method_names[self] ||= {}).keys
136 end
137
138 # Rolls up all of the test* methods in the fixture into
139 # one suite, creating a new instance of the fixture for
140 # each method.
141 def suite
142 suite_creator = TestSuiteCreator.new(self)
143 suite_creator.create
144 end
145
146 # Called before every test case runs. Can be used
147 # to set up fixture information used in test case
148 # scope.
149 #
150 # Here is an example test case:
151 # class TestMyClass < Test::Unit::TestCase
152 # class << self
153 # def startup
154 # ...
155 # end
156 # end
157 #
158 # def setup
159 # ...
160 # end
161 #
162 # def test_my_class1
163 # ...
164 # end
165 #
166 # def test_my_class2
167 # ...
168 # end
169 # end
170 #
171 # Here is a call order:
172 # * startup
173 # * setup
174 # * test_my_class1 (or test_my_class2)
175 # * setup
176 # * test_my_class2 (or test_my_class1)
177 #
178 # Note that you should not assume test order. Tests
179 # should be worked in any order.
180 def startup
181 end
182
183 # Called after every test case runs. Can be used to tear
184 # down fixture information used in test case scope.
185 #
186 # Here is an example test case:
187 # class TestMyClass < Test::Unit::TestCase
188 # class << self
189 # def shutdown
190 # ...
191 # end
192 # end
193 #
194 # def teardown
195 # ...
196 # end
197 #
198 # def test_my_class1
199 # ...
200 # end
201 #
202 # def test_my_class2
203 # ...
204 # end
205 # end
206 #
207 # Here is a call order:
208 # * test_my_class1 (or test_my_class2)
209 # * teardown
210 # * test_my_class2 (or test_my_class1)
211 # * teardown
212 # * shutdown
213 #
214 # Note that you should not assume test order. Tests
215 # should be worked in any order.
216 def shutdown
217 end
218
219 @@test_orders = {}
220
221 # Returns the current test order. This returns
222 # +:alphabetic+ by default.
223 def test_order
224 @@test_orders[self] || AVAILABLE_ORDERS.first
225 end
226
227 # Sets the current test order.
228 #
229 # Here are the available _order_:
230 # [:alphabetic]
231 # Default. Tests are sorted in alphabetic order.
232 # [:random]
233 # Tests are sorted in random order.
234 # [:defined]
235 # Tests are sorted in defined order.
236 def test_order=(order)
237 @@test_orders[self] = order
238 end
239
240 # Defines a test in declarative syntax or marks
241 # following method as a test method.
242 #
243 # In declarative syntax usage, the following two
244 # test definitions are the almost same:
245 #
246 # description "register user"
247 # def test_register_user
248 # ...
249 # end
250 #
251 # test "register user" do
252 # ...
253 # end
254 #
255 # In test method mark usage, the "my_test_method" is
256 # treated as a test method:
257 #
258 # test
259 # def my_test_method
260 # assert_equal("call me", ...)
261 # end
262 def test(*test_description_or_targets, &block)
263 if block_given?
264 test_description = test_description_or_targets.first
265 if test_description.nil?
266 raise ArgumentError, "test description is missing"
267 end
268 n_arguments = test_description_or_targets.size
269 if n_arguments > 1
270 message = "wrong number of arguments (#{n_arguments} for 1)"
271 raise ArgumentError, message
272 end
273 method_name = "test: #{test_description}"
274 description(test_description, method_name)
275 attribute(:test, true, {}, method_name)
276 if block.respond_to?(:source_location)
277 attribute(:source_location, block.source_location, {}, method_name)
278 end
279 define_method(method_name, &block)
280 else
281 targets = test_description_or_targets
282 attribute(:test, true, {}, *targets)
283 end
284 end
285
286 # Describes a test.
287 #
288 # The following example associates "register a
289 # normal user" description with "test_register"
290 # test.
291 #
292 # description "register a normal user"
293 # def test_register
294 # ...
295 # end
296 def description(value, target=nil)
297 targets = [target].compact
298 attribute(:description, value, {}, *targets)
299 end
300
301 # Defines a sub test case.
302 #
303 # This is a syntax sugar. The both of the following codes are
304 # the same in meaning:
305 #
306 # Standard:
307 # class TestParent < Test::UnitTestCase
308 # class TestChild < self
309 # def test_in_child
310 # end
311 # end
312 # end
313 #
314 # Syntax sugar:
315 # class TestParent < Test::UnitTestCase
316 # sub_test_case("TestChild") do
317 # def test_in_child
318 # end
319 # end
320 # end
321 #
322 # The difference of them are the following:
323 #
324 # * Test case created by {sub_test_case} is an anonymous class.
325 # So you can't refer the test case by name.
326 # * The class name of class style must follow
327 # constant naming rule in Ruby. But the name of test case
328 # created by {sub_test_case} doesn't need to follow the rule.
329 # For example, you can use a space in name such as "child test".
330 #
331 # @param name [String] The name of newly created sub test case.
332 # @yield
333 # The block is evaluated under the newly created sub test
334 # case class context.
335 # @return [Test::Unit::TestCase] Created sub test case class.
336 def sub_test_case(name, &block)
337 parent_test_case = self
338 sub_test_case = Class.new(self) do
339 singleton_class = class << self; self; end
340 singleton_class.__send__(:define_method, :name) do
341 [parent_test_case.name, name].compact.join("::")
342 end
343 end
344 sub_test_case.class_eval(&block)
345 sub_test_case
346 end
347
348 # Checks whether a test that is matched the query is
349 # defined.
350 #
351 # @option query [String] :path (nil)
352 # the path where a test is defined in.
353 # @option query [Numeric] :line (nil)
354 # the line number where a test is defined at.
355 # @option query [String] :method_name (nil)
356 # the method name for a test.
357 def test_defined?(query)
358 query_path = query[:path]
359 query_line = query[:line]
360 query_method_name = query[:method_name]
361
362 available_locations = target_method_locations(query_path)
363 if query_line
364 available_locations = available_locations.sort_by do |location|
365 -location[:line]
366 end
367 available_location = available_locations.find do |location|
368 query_line >= location[:line]
369 end
370 return false if available_location.nil?
371 return false if available_location[:test_case] != self
372 available_locations = [available_location]
373 end
374 if query_method_name
375 available_location = available_locations.find do |location|
376 query_method_name == location[:method_name]
377 end
378 return false if available_location.nil?
379 available_locations = [available_location]
380 end
381
382 not available_locations.empty?
383 end
384
385 private
386 # @private
387 @@method_locations = {}
388 # @private
389 def method_locations
390 @@method_locations[self] ||= []
391 end
392
393 # @private
394 def target_method_locations(path)
395 if path.nil?
396 self_location = method_locations.first
397 path = self_location[:path] if self_location
398 end
399 return [] if path.nil?
400
401 target_locations = []
402 @@method_locations.each do |test_case, locations|
403 locations.each do |location|
404 absolete_path = File.expand_path(path)
405 location_path = location[:path]
406 location_basename = File.basename(location_path)
407 if location_path == absolete_path or location_basename == path
408 target_locations << location.merge(:test_case => test_case)
409 end
410 end
411 end
412 target_locations
413 end
414 end
415
416 attr_reader :method_name
417
418 # Creates a new instance of the fixture for running the
419 # test represented by test_method_name.
420 def initialize(test_method_name)
421 @method_name = test_method_name
422 @internal_data = InternalData.new
423 end
424
425 # Assigns test data to the test. It is used in internal.
426 def assign_test_data(label, data) # :nodoc:
427 @internal_data.assign_test_data(label, data)
428 end
429
430 # Returns the test is valid test. It is used in internal.
431 def valid? # :nodoc:
432 return false unless respond_to?(@method_name)
433 test_method = method(@method_name)
434 if @internal_data.have_test_data?
435 return false unless test_method.arity == 1
436 else
437 return false unless test_method.arity <= 0
438 end
439 owner = Util::MethodOwnerFinder.find(self, @method_name)
440 if owner.class != Module and self.class != owner
441 return false
442 end
443 true
444 end
445
446 # Runs the individual test method represented by this
447 # instance of the fixture, collecting statistics, failures
448 # and errors in result.
449 def run(result)
450 begin
451 @_result = result
452 @internal_data.test_started
453 yield(STARTED, name)
454 yield(STARTED_OBJECT, self)
455 begin
456 run_setup
457 run_test
458 run_cleanup
459 add_pass
460 rescue Exception
461 @internal_data.interrupted
462 raise unless handle_exception($!)
463 ensure
464 begin
465 run_teardown
466 rescue Exception
467 raise unless handle_exception($!)
468 end
469 end
470 @internal_data.test_finished
471 result.add_run
472 yield(FINISHED, name)
473 yield(FINISHED_OBJECT, self)
474 ensure
475 # @_result = nil # For test-spec's after_all :<
476 end
477 end
478
479 # Called before every test method runs. Can be used
480 # to set up fixture information.
481 #
482 # You can add additional setup tasks by the following
483 # code:
484 # class TestMyClass < Test::Unit::TestCase
485 # def setup
486 # ...
487 # end
488 #
489 # setup
490 # def my_setup1
491 # ...
492 # end
493 #
494 # setup do
495 # ... # setup callback1
496 # end
497 #
498 # setup
499 # def my_setup2
500 # ...
501 # end
502 #
503 # setup do
504 # ... # setup callback2
505 # end
506 #
507 # def test_my_class
508 # ...
509 # end
510 # end
511 #
512 # Here is a call order:
513 # * setup
514 # * my_setup1
515 # * setup callback1
516 # * my_setup2
517 # * setup callback2
518 # * test_my_class
519 def setup
520 end
521
522 # Called after every test method runs but the test
523 # method isn't marked as 'passed'. Can be used to
524 # clean up and/or verify tested condition.
525 # e.g. Can be used to verify mock.
526 #
527 # You can add additional cleanup tasks by the following
528 # code:
529 # class TestMyClass < Test::Unit::TestCase
530 # def cleanup
531 # ...
532 # end
533 #
534 # cleanup
535 # def my_cleanup1
536 # ...
537 # end
538 #
539 # cleanup do
540 # ... # cleanup callback1
541 # end
542 #
543 # cleanup
544 # def my_cleanup2
545 # ...
546 # end
547 #
548 # cleanup do
549 # ... # cleanup callback2
550 # end
551 #
552 # def test_my_class
553 # ...
554 # end
555 # end
556 #
557 # Here is a call order:
558 # * test_my_class
559 # * cleanup callback2
560 # * my_cleanup2
561 # * cleanup callback1
562 # * my_cleanup1
563 # * cleanup
564 def cleanup
565 end
566
567 # Called after every test method runs. Can be used to tear
568 # down fixture information.
569 #
570 # You can add additional teardown tasks by the following
571 # code:
572 # class TestMyClass < Test::Unit::TestCase
573 # def teardown
574 # ...
575 # end
576 #
577 # teardown
578 # def my_teardown1
579 # ...
580 # end
581 #
582 # teardown do
583 # ... # teardown callback1
584 # end
585 #
586 # teardown
587 # def my_teardown2
588 # ...
589 # end
590 #
591 # teardown do
592 # ... # teardown callback2
593 # end
594 #
595 # def test_my_class
596 # ...
597 # end
598 # end
599 #
600 # Here is a call order:
601 # * test_my_class
602 # * teardown callback2
603 # * my_teardown2
604 # * teardown callback1
605 # * my_teardown1
606 # * teardown
607 def teardown
608 end
609
610 def default_test
611 flunk("No tests were specified")
612 end
613
614 def size
615 1
616 end
617
618 # Returns a label of test data for the test. If the
619 # test isn't associated with any test data, it returns
620 # +nil+.
621 def data_label
622 @internal_data.test_data_label
623 end
624
625 # Returns a human-readable name for the specific test that
626 # this instance of TestCase represents.
627 def name
628 if @internal_data.have_test_data?
629 "#{@method_name}[#{data_label}](#{self.class.name})"
630 else
631 "#{@method_name}(#{self.class.name})"
632 end
633 end
634
635 # Returns a description for the test. A description
636 # will be associated by Test::Unit::TestCase.test or
637 # Test::Unit::TestCase.description.
638 #
639 # Returns a name for the test for no description test.
640 def description
641 self[:description] || name
642 end
643
644 # Overridden to return #name.
645 def to_s
646 name
647 end
648
649 # It's handy to be able to compare TestCase instances.
650 def ==(other)
651 return false unless other.kind_of?(self.class)
652 return false unless @method_name == other.method_name
653 return false unless data_label == other.data_label
654 self.class == other.class
655 end
656
657 # Returns a Time at the test was started.
658 def start_time
659 @internal_data.start_time
660 end
661
662 # Returns elapsed time for the test was ran.
663 def elapsed_time
664 @internal_data.elapsed_time
665 end
666
667 # Returns whether the test is interrupted.
668 def interrupted?
669 @internal_data.interrupted?
670 end
671
672 # Returns whether this individual test passed or
673 # not. Primarily for use in teardown so that artifacts
674 # can be left behind if the test fails.
675 def passed?
676 @internal_data.passed?
677 end
678
679 # Notify that a problem is occurred in the test. It means that
680 # the test is a failed test. If any failed tests exist in test
681 # suites, the test process exits with failure exit status.
682 #
683 # This is a public API for developers who extend test-unit.
684 #
685 # @return [void]
686 def problem_occurred
687 @internal_data.problem_occurred
688 end
689
690 # Notify that the test is passed. Normally, it is not needed
691 # because #run calls it automatically. If you want to override
692 # #run, it is not a good idea. Please contact test-unit
693 # developers. We will help you without your custom #run. For
694 # example, we may add a new hook in #run.
695 #
696 # This is a public API for developers who extend test-unit.
697 #
698 # @return [void]
699 def add_pass
700 current_result.add_pass
701 end
702
703 private
704 def current_result
705 @_result
706 end
707
708 def run_test
709 redefined_info = self[:redefined]
710 if redefined_info
711 notify("#{self.class}\##{@method_name} was redefined",
712 :backtrace => redefined_info[:backtrace])
713 end
714 if @internal_data.have_test_data?
715 __send__(@method_name, @internal_data.test_data)
716 else
717 __send__(@method_name)
718 end
719 end
720
721 def handle_exception(exception)
722 self.class.exception_handlers.each do |handler|
723 if handler.respond_to?(:call)
724 handled = handler.call(self, exception)
725 else
726 handled = __send__(handler, exception)
727 end
728 return true if handled
729 end
730 false
731 end
732
733 def add_assertion
734 current_result.add_assertion
735 end
736
737 class InternalData
738 attr_reader :start_time, :elapsed_time
739 attr_reader :test_data_label, :test_data
740 def initialize
741 @start_time = nil
742 @elapsed_time = nil
743 @passed = true
744 @interrupted = false
745 @test_data_label = nil
746 @test_data = nil
747 end
748
749 def passed?
750 @passed
751 end
752
753 def interrupted?
754 @interrupted
755 end
756
757 def assign_test_data(label, data)
758 @test_data_label = label
759 @test_data = data
760 end
761
762 def have_test_data?
763 not @test_data_label.nil?
764 end
765
766 def test_started
767 @start_time = Time.now
768 end
769
770 def test_finished
771 @elapsed_time = Time.now - @start_time
772 end
773
774 def problem_occurred
775 @passed = false
776 end
777
778 def interrupted
779 @interrupted = true
780 end
781 end
782 end
783 end
784 end
+0
-125
vendor/test/unit/testresult.rb less more
0 #--
1 # Author:: Nathaniel Talbott.
2 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
3 # License:: Ruby license.
4
5 require 'test/unit/util/observable'
6 require 'test/unit/failure'
7 require 'test/unit/error'
8 require 'test/unit/omission'
9 require 'test/unit/pending'
10 require 'test/unit/notification'
11
12 module Test
13 module Unit
14 module NullResultContainerInitializer
15 private
16 def initialize_containers
17 end
18 end
19
20 # Collects Test::Unit::Failure and Test::Unit::Error so that
21 # they can be displayed to the user. To this end, observers
22 # can be added to it, allowing the dynamic updating of, say, a
23 # UI.
24 class TestResult
25 include Util::Observable
26 include NullResultContainerInitializer
27 include TestResultFailureSupport
28 include TestResultErrorSupport
29 include TestResultPendingSupport
30 include TestResultOmissionSupport
31 include TestResultNotificationSupport
32
33 FINISHED = name + "::FINISHED"
34 CHANGED = name + "::CHANGED"
35 PASS_ASSERTION = name + "::PASS_ASSERTION"
36 FAULT = name + "::FAULT"
37
38 attr_reader :run_count, :pass_count, :assertion_count, :faults
39
40 # Constructs a new, empty TestResult.
41 def initialize
42 @run_count, @pass_count, @assertion_count = 0, 0, 0
43 @summary_generators = []
44 @problem_checkers = []
45 @faults = []
46 initialize_containers
47 end
48
49 # Records a test run.
50 def add_run
51 @run_count += 1
52 notify_listeners(FINISHED, self)
53 notify_changed
54 end
55
56 def add_pass
57 @pass_count += 1
58 end
59
60 # Records an individual assertion.
61 def add_assertion
62 @assertion_count += 1
63 notify_listeners(PASS_ASSERTION, self)
64 notify_changed
65 end
66
67 # Returns a string contain the recorded runs, assertions,
68 # failures and errors in this TestResult.
69 def summary
70 ["#{run_count} tests",
71 "#{assertion_count} assertions",
72 *@summary_generators.collect {|generator| __send__(generator)}].join(", ")
73 end
74
75 # Returnes a string that shows result status.
76 def status
77 if passed?
78 if pending_count > 0
79 "pending"
80 elsif omission_count > 0
81 "omission"
82 elsif notification_count > 0
83 "notification"
84 else
85 "pass"
86 end
87 elsif error_count > 0
88 "error"
89 elsif failure_count > 0
90 "failure"
91 end
92 end
93
94 def to_s
95 summary
96 end
97
98 # Returns whether or not this TestResult represents
99 # successful completion.
100 def passed?
101 @problem_checkers.all? {|checker| not __send__(checker)}
102 end
103
104 def pass_percentage
105 n_tests = @run_count - omission_count
106 if n_tests.zero?
107 0
108 else
109 100.0 * (@pass_count / n_tests.to_f)
110 end
111 end
112
113 private
114 def notify_changed
115 notify_listeners(CHANGED, self)
116 end
117
118 def notify_fault(fault)
119 @faults << fault
120 notify_listeners(FAULT, fault)
121 end
122 end
123 end
124 end
+0
-175
vendor/test/unit/testsuite.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
4 # Copyright:: Copyright (c) 2008-2011 Kouhei Sutou. All rights reserved.
5 # License:: Ruby license.
6
7 require 'test/unit/error'
8
9 module Test
10 module Unit
11
12 # A collection of tests which can be #run.
13 #
14 # Note: It is easy to confuse a TestSuite instance with
15 # something that has a static suite method; I know because _I_
16 # have trouble keeping them straight. Think of something that
17 # has a suite method as simply providing a way to get a
18 # meaningful TestSuite instance.
19 class TestSuite
20 attr_reader :name, :tests, :test_case, :start_time, :elapsed_time
21
22 # Test suite that has higher priority is ran prior to
23 # test suites that have lower priority.
24 attr_accessor :priority
25
26 STARTED = name + "::STARTED"
27 STARTED_OBJECT = name + "::STARTED::OBJECT"
28 FINISHED = name + "::FINISHED"
29 FINISHED_OBJECT = name + "::FINISHED::OBJECT"
30
31 # Creates a new TestSuite with the given name.
32 def initialize(name="Unnamed TestSuite", test_case=nil)
33 @name = name
34 @tests = []
35 @test_case = test_case
36 @n_tests = 0
37 @priority = 0
38 @start_time = nil
39 @elapsed_time = nil
40 @passed = true
41 end
42
43 # Runs the tests and/or suites contained in this
44 # TestSuite.
45 def run(result, &progress_block)
46 @start_time = Time.now
47 yield(STARTED, name)
48 yield(STARTED_OBJECT, self)
49 run_startup(result)
50 while test = @tests.shift
51 @n_tests += test.size
52 run_test(test, result, &progress_block)
53 @passed = false unless test.passed?
54 end
55 ensure
56 begin
57 run_shutdown(result)
58 ensure
59 @elapsed_time = Time.now - @start_time
60 yield(FINISHED, name)
61 yield(FINISHED_OBJECT, self)
62 end
63 end
64
65 # Adds the test to the suite.
66 def <<(test)
67 @tests << test
68 self
69 end
70
71 def delete(test)
72 @tests.delete(test)
73 end
74
75 def delete_tests(tests)
76 @tests -= tests
77 end
78
79 # Retuns the rolled up number of tests in this suite;
80 # i.e. if the suite contains other suites, it counts the
81 # tests within those suites, not the suites themselves.
82 def size
83 total_size = @n_tests
84 @tests.each { |test| total_size += test.size }
85 total_size
86 end
87
88 def empty?
89 size.zero?
90 end
91
92 # Overridden to return the name given the suite at
93 # creation.
94 def to_s
95 @name
96 end
97
98 # It's handy to be able to compare TestSuite instances.
99 def ==(other)
100 return false unless(other.kind_of?(self.class))
101 return false unless(@name == other.name)
102 @tests == other.tests
103 end
104
105 def passed?
106 @passed
107 end
108
109 private
110 def run_startup(result)
111 return if @test_case.nil? or !@test_case.respond_to?(:startup)
112 begin
113 @test_case.startup
114 rescue Exception
115 raise unless handle_exception($!, result)
116 end
117 end
118
119 def run_test(test, result)
120 finished_is_yielded = false
121 finished_object_is_yielded = false
122 previous_event_name = nil
123 test.run(result) do |event_name, *args|
124 case previous_event_name
125 when Test::Unit::TestCase::STARTED
126 if event_name != Test::Unit::TestCase::STARTED_OBJECT
127 yield(Test::Unit::TestCase::STARTED_OBJECT, test)
128 end
129 when Test::Unit::TestCase::FINISHED
130 if event_name != Test::Unit::TestCase::FINISHED_OBJECT
131 yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
132 end
133 finished_object_is_yielded = true
134 end
135
136 case event_name
137 when Test::Unit::TestCase::STARTED
138 finished_is_yielded = false
139 finished_object_is_yielded = false
140 when Test::Unit::TestCase::FINISHED
141 finished_is_yielded = true
142 end
143
144 previous_event_name = event_name
145 yield(event_name, *args)
146 end
147
148 if finished_is_yielded and not finished_object_is_yielded
149 yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
150 end
151 end
152
153 def run_shutdown(result)
154 return if @test_case.nil? or !@test_case.respond_to?(:shutdown)
155 begin
156 @test_case.shutdown
157 rescue Exception
158 raise unless handle_exception($!, result)
159 end
160 end
161
162 def handle_exception(exception, result)
163 case exception
164 when *ErrorHandler::PASS_THROUGH_EXCEPTIONS
165 false
166 else
167 result.add_error(Error.new(@test_case.name, exception))
168 @passed = false
169 true
170 end
171 end
172 end
173 end
174 end
+0
-15
vendor/test/unit/ui/console/outputlevel.rb less more
0 module Test
1 module Unit
2 module UI
3 module Console
4 module OutputLevel
5 SILENT = 0
6 PROGRESS_ONLY = 1
7 IMPORTANT_FAULTS_ONLY = 2
8 NORMAL = 3
9 VERBOSE = 4
10 end
11 end
12 end
13 end
14 end
+0
-693
vendor/test/unit/ui/console/testrunner.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright::
4 # * Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
5 # * Copyright (c) 2008-2013 Kouhei Sutou <kou@clear-code.com>
6 # License:: Ruby license.
7
8 begin
9 require 'io/console'
10 rescue LoadError
11 end
12
13 require 'test/unit/color-scheme'
14 require 'test/unit/code-snippet-fetcher'
15 require 'test/unit/fault-location-detector'
16 require 'test/unit/diff'
17 require 'test/unit/ui/testrunner'
18 require 'test/unit/ui/testrunnermediator'
19 require 'test/unit/ui/console/outputlevel'
20
21 module Test
22 module Unit
23 module UI
24 module Console
25
26 # Runs a Test::Unit::TestSuite on the console.
27 class TestRunner < UI::TestRunner
28 include OutputLevel
29
30 # Creates a new TestRunner for running the passed
31 # suite. If quiet_mode is true, the output while
32 # running is limited to progress dots, errors and
33 # failures, and the final result. io specifies
34 # where runner output should go to; defaults to
35 # STDOUT.
36 def initialize(suite, options={})
37 super
38 @output_level = @options[:output_level] || NORMAL
39 @output = @options[:output] || STDOUT
40 @use_color = @options[:use_color]
41 @use_color = guess_color_availability if @use_color.nil?
42 @color_scheme = @options[:color_scheme] || ColorScheme.default
43 @reset_color = Color.new("reset")
44 @progress_row = 0
45 @progress_row_max = @options[:progress_row_max]
46 @progress_row_max ||= guess_progress_row_max
47 @show_detail_immediately = @options[:show_detail_immediately]
48 @show_detail_immediately = true if @show_detail_immediately.nil?
49 @already_outputted = false
50 @indent = 0
51 @top_level = true
52 @current_output_level = NORMAL
53 @faults = []
54 @code_snippet_fetcher = CodeSnippetFetcher.new
55 @test_suites = []
56 end
57
58 private
59 def change_output_level(level)
60 old_output_level = @current_output_level
61 @current_output_level = level
62 yield
63 @current_output_level = old_output_level
64 end
65
66 def setup_mediator
67 super
68 output_setup_end
69 end
70
71 def output_setup_end
72 suite_name = @suite.to_s
73 suite_name = @suite.name if @suite.kind_of?(Module)
74 output("Loaded suite #{suite_name}")
75 end
76
77 def attach_to_mediator
78 @mediator.add_listener(TestResult::FAULT,
79 &method(:add_fault))
80 @mediator.add_listener(TestRunnerMediator::STARTED,
81 &method(:started))
82 @mediator.add_listener(TestRunnerMediator::FINISHED,
83 &method(:finished))
84 @mediator.add_listener(TestCase::STARTED_OBJECT,
85 &method(:test_started))
86 @mediator.add_listener(TestCase::FINISHED_OBJECT,
87 &method(:test_finished))
88 @mediator.add_listener(TestSuite::STARTED_OBJECT,
89 &method(:test_suite_started))
90 @mediator.add_listener(TestSuite::FINISHED_OBJECT,
91 &method(:test_suite_finished))
92 end
93
94 def add_fault(fault)
95 @faults << fault
96 output_progress(fault.single_character_display,
97 fault_marker_color(fault))
98 output_progress_in_detail(fault) if @show_detail_immediately
99 @already_outputted = true if fault.critical?
100 end
101
102 def started(result)
103 @result = result
104 output_started
105 end
106
107 def output_started
108 output("Started")
109 end
110
111 def finished(elapsed_time)
112 nl if output?(NORMAL) and !output?(VERBOSE)
113 output_faults unless @show_detail_immediately
114 nl(PROGRESS_ONLY)
115 change_output_level(IMPORTANT_FAULTS_ONLY) do
116 output_statistics(elapsed_time)
117 end
118 end
119
120 def output_faults
121 categorized_faults = categorize_faults
122 change_output_level(IMPORTANT_FAULTS_ONLY) do
123 output_faults_in_detail(categorized_faults[:need_detail_faults])
124 end
125 output_faults_in_short("Omissions", Omission,
126 categorized_faults[:omissions])
127 output_faults_in_short("Notifications", Notification,
128 categorized_faults[:notifications])
129 end
130
131 def max_digit(max_number)
132 (Math.log10(max_number) + 1).truncate
133 end
134
135 def output_faults_in_detail(faults)
136 return if faults.nil?
137 digit = max_digit(faults.size)
138 faults.each_with_index do |fault, index|
139 nl
140 output_single("%#{digit}d) " % (index + 1))
141 output_fault_in_detail(fault)
142 end
143 end
144
145 def output_faults_in_short(label, fault_class, faults)
146 return if faults.nil?
147 digit = max_digit(faults.size)
148 nl
149 output_single(label, fault_class_color(fault_class))
150 output(":")
151 faults.each_with_index do |fault, index|
152 output_single("%#{digit}d) " % (index + 1))
153 output_fault_in_short(fault)
154 end
155 end
156
157 def categorize_faults
158 faults = {}
159 @faults.each do |fault|
160 category = categorize_fault(fault)
161 faults[category] ||= []
162 faults[category] << fault
163 end
164 faults
165 end
166
167 def categorize_fault(fault)
168 case fault
169 when Omission
170 :omissions
171 when Notification
172 :notifications
173 else
174 :need_detail_faults
175 end
176 end
177
178 def output_fault_in_detail(fault)
179 if fault.is_a?(Failure) and
180 fault.inspected_expected and fault.inspected_actual
181 output_single("#{fault.label}: ")
182 output(fault.test_name, fault_color(fault))
183 output_fault_backtrace(fault)
184 output_failure_message(fault)
185 else
186 if fault.is_a?(Error)
187 output_single("#{fault.label}: ")
188 output_single(fault.test_name, fault_color(fault))
189 output_fault_message(fault)
190 else
191 output_single(fault.label, fault_color(fault))
192 output_fault_message(fault)
193 output(fault.test_name)
194 end
195 output_fault_backtrace(fault)
196 end
197 end
198
199 def output_fault_message(fault)
200 message = fault.message
201 return if message.nil?
202
203 if message.include?("\n")
204 output(":")
205 message.each_line do |line|
206 output(" #{line.chomp}")
207 end
208 else
209 output(": #{message}")
210 end
211 end
212
213 def output_fault_backtrace(fault)
214 snippet_is_shown = false
215 detector = FaultLocationDetector.new(fault, @code_snippet_fetcher)
216 backtrace = fault.location
217 # workaround for test-spec. :<
218 # see also GitHub:#22
219 backtrace ||= []
220 backtrace.each_with_index do |entry, i|
221 output(entry)
222 next if snippet_is_shown
223 next unless detector.target?(entry)
224 file, line_number, = detector.split_backtrace_entry(entry)
225 snippet_is_shown = output_code_snippet(file, line_number,
226 fault_color(fault))
227 end
228 end
229
230 def output_code_snippet(file, line_number, target_line_color=nil)
231 lines = @code_snippet_fetcher.fetch(file, line_number)
232 return false if lines.empty?
233
234 max_n = lines.collect {|n, line, attributes| n}.max
235 digits = (Math.log10(max_n) + 1).truncate
236 lines.each do |n, line, attributes|
237 if attributes[:target_line?]
238 line_color = target_line_color
239 current_line_mark = "=>"
240 else
241 line_color = nil
242 current_line_mark = ""
243 end
244 output(" %2s %*d: %s" % [current_line_mark, digits, n, line],
245 line_color)
246 end
247 true
248 end
249
250 def output_failure_message(failure)
251 if failure.expected.respond_to?(:encoding) and
252 failure.actual.respond_to?(:encoding) and
253 failure.expected.encoding != failure.actual.encoding
254 need_encoding = true
255 else
256 need_encoding = false
257 end
258 output(failure.user_message) if failure.user_message
259 output_single("<")
260 output_single(failure.inspected_expected, color("pass"))
261 output_single(">")
262 if need_encoding
263 output_single("(")
264 output_single(failure.expected.encoding.name, color("pass"))
265 output_single(")")
266 end
267 output(" expected but was")
268 output_single("<")
269 output_single(failure.inspected_actual, color("failure"))
270 output_single(">")
271 if need_encoding
272 output_single("(")
273 output_single(failure.actual.encoding.name, color("failure"))
274 output_single(")")
275 end
276 output("")
277 from, to = prepare_for_diff(failure.expected, failure.actual)
278 if from and to
279 if need_encoding
280 unless from.valid_encoding?
281 from = from.dup.force_encoding("ASCII-8BIT")
282 end
283 unless to.valid_encoding?
284 to = to.dup.force_encoding("ASCII-8BIT")
285 end
286 end
287 from_lines = from.split(/\r?\n/)
288 to_lines = to.split(/\r?\n/)
289 if need_encoding
290 from_lines << ""
291 to_lines << ""
292 from_lines << "Encoding: #{failure.expected.encoding.name}"
293 to_lines << "Encoding: #{failure.actual.encoding.name}"
294 end
295 differ = ColorizedReadableDiffer.new(from_lines, to_lines, self)
296 if differ.need_diff?
297 output("")
298 output("diff:")
299 differ.diff
300 end
301 end
302 end
303
304 def output_fault_in_short(fault)
305 output_single(fault.message, fault_color(fault))
306 output(" [#{fault.test_name}]")
307 output(fault.location.first)
308 end
309
310 def format_fault(fault)
311 fault.long_display
312 end
313
314 def output_statistics(elapsed_time)
315 output("Finished in #{elapsed_time} seconds.")
316 output_summary_marker
317 output(@result)
318 output("%g%% passed" % @result.pass_percentage)
319 unless elapsed_time.zero?
320 output_summary_marker
321 test_throughput = @result.run_count / elapsed_time
322 assertion_throughput = @result.assertion_count / elapsed_time
323 throughput = [
324 "%.2f tests/s" % test_throughput,
325 "%.2f assertions/s" % assertion_throughput,
326 ]
327 output(throughput.join(", "))
328 end
329 end
330
331 def output_summary_marker
332 term_width = guess_term_width
333 if term_width.zero?
334 marker_width = 6
335 else
336 marker_width = term_width
337 end
338 output("-" * marker_width, summary_marker_color)
339 end
340
341 def test_started(test)
342 return unless output?(VERBOSE)
343
344 name = test.name.sub(/\(.+?\)\z/, '')
345 right_space = 8 * 2
346 left_space = @progress_row_max - right_space
347 left_space = left_space - indent.size - name.size
348 tab_stop = "\t" * ([left_space - 1, 0].max / 8)
349 output_single("#{indent}#{name}:#{tab_stop}", nil, VERBOSE)
350 @test_start = Time.now
351 end
352
353 def test_finished(test)
354 unless @already_outputted
355 output_progress(".", color("pass-marker"))
356 end
357 @already_outputted = false
358
359 return unless output?(VERBOSE)
360
361 output(": (%f)" % (Time.now - @test_start), nil, VERBOSE)
362 end
363
364 def suite_name(prefix, suite)
365 name = suite.name
366 if name.nil?
367 "(anonymous)"
368 else
369 name.sub(/\A#{Regexp.escape(prefix)}/, "")
370 end
371 end
372
373 def test_suite_started(suite)
374 last_test_suite = @test_suites.last
375 @test_suites << suite
376 if @top_level
377 @top_level = false
378 return
379 end
380
381 output_single(indent, nil, VERBOSE)
382 if suite.test_case.nil?
383 _color = color("suite")
384 else
385 _color = color("case")
386 end
387 prefix = "#{last_test_suite.name}::"
388 output_single(suite_name(prefix, suite), _color, VERBOSE)
389 output(": ", nil, VERBOSE)
390 @indent += 2
391 end
392
393 def test_suite_finished(suite)
394 @indent -= 2
395 @test_suites.pop
396 end
397
398 def indent
399 if output?(VERBOSE)
400 " " * @indent
401 else
402 ""
403 end
404 end
405
406 def nl(level=nil)
407 output("", nil, level)
408 end
409
410 def output(something, color=nil, level=nil)
411 return unless output?(level)
412 output_single(something, color, level)
413 @output.puts
414 end
415
416 def output_single(something, color=nil, level=nil)
417 return false unless output?(level)
418 if @use_color and color
419 something = "%s%s%s" % [color.escape_sequence,
420 something,
421 @reset_color.escape_sequence]
422 end
423 @output.write(something)
424 @output.flush
425 true
426 end
427
428 def output_progress(mark, color=nil)
429 if output_single(mark, color, PROGRESS_ONLY)
430 return unless @progress_row_max > 0
431 @progress_row += mark.size
432 if @progress_row >= @progress_row_max
433 nl unless @output_level == VERBOSE
434 @progress_row = 0
435 end
436 end
437 end
438
439 def output_progress_in_detail_marker(fault)
440 if @progress_row_max > 0
441 output("=" * @progress_row_max)
442 else
443 nl
444 end
445 end
446
447 def output_progress_in_detail(fault)
448 return if @output_level == SILENT
449 nl
450 output_progress_in_detail_marker(fault)
451 if categorize_fault(fault) == :need_detail_faults
452 output_fault_in_detail(fault)
453 else
454 output_fault_in_short(fault)
455 end
456 output_progress_in_detail_marker(fault)
457 @progress_row = 0
458 end
459
460 def output?(level)
461 (level || @current_output_level) <= @output_level
462 end
463
464 def color(name)
465 _color = @color_scheme[name]
466 _color ||= @color_scheme["success"] if name == "pass"
467 _color ||= ColorScheme.default[name]
468 _color
469 end
470
471 def fault_class_color_name(fault_class)
472 fault_class.name.split(/::/).last.downcase
473 end
474
475 def fault_color(fault)
476 color(fault_class_color_name(fault.class))
477 end
478
479 def fault_marker_color(fault)
480 color("#{fault_class_color_name(fault.class)}-marker")
481 end
482
483 def summary_marker_color
484 color("#{@result.status}-marker")
485 end
486
487 def guess_color_availability
488 return false unless @output.tty?
489 return true if windows? and ruby_2_0_or_later?
490 case ENV["TERM"]
491 when /(?:term|screen)(?:-(?:256)?color)?\z/
492 true
493 else
494 return true if ENV["EMACS"] == "t"
495 false
496 end
497 end
498
499 def windows?
500 /mswin|mingw/ === RUBY_PLATFORM
501 end
502
503 def ruby_2_0_or_later?
504 RUBY_VERSION >= "2.0.0"
505 end
506
507 def guess_progress_row_max
508 term_width = guess_term_width
509 if term_width.zero?
510 if ENV["EMACS"] == "t"
511 -1
512 else
513 79
514 end
515 else
516 term_width
517 end
518 end
519
520 def guess_term_width
521 guess_term_width_from_io || guess_term_width_from_env || 0
522 end
523
524 def guess_term_width_from_io
525 if @output.respond_to?(:winsize)
526 begin
527 @output.winsize[1]
528 rescue SystemCallError
529 nil
530 end
531 else
532 nil
533 end
534 end
535
536 def guess_term_width_from_env
537 env = ENV["COLUMNS"] || ENV["TERM_WIDTH"]
538 return nil if env.nil?
539
540 begin
541 Integer(env)
542 rescue ArgumentError
543 nil
544 end
545 end
546 end
547
548 class ColorizedReadableDiffer < Diff::ReadableDiffer
549 def initialize(from, to, runner)
550 @runner = runner
551 super(from, to)
552 end
553
554 def need_diff?(options={})
555 return false if one_line_all_change?
556 operations.each do |tag,|
557 return true if [:replace, :equal].include?(tag)
558 end
559 false
560 end
561
562 private
563 def one_line_all_change?
564 return false if operations.size != 1
565
566 tag, from_start, from_end, to_start, to_end = operations.first
567 return false if tag != :replace
568 return false if [from_start, from_end] != [0, 1]
569 return false if [from_start, from_end] != [to_start, to_end]
570
571 _, _, _line_operations = line_operations(@from.first, @to.first)
572 _line_operations.size == 1
573 end
574
575 def output_single(something, color=nil)
576 @runner.__send__(:output_single, something, color)
577 end
578
579 def output(something, color=nil)
580 @runner.__send__(:output, something, color)
581 end
582
583 def color(name)
584 @runner.__send__(:color, name)
585 end
586
587 def cut_off_ratio
588 0
589 end
590
591 def default_ratio
592 0
593 end
594
595 def tag(mark, color_name, contents)
596 _color = color(color_name)
597 contents.each do |content|
598 output_single(mark, _color)
599 output_single(" ")
600 output(content)
601 end
602 end
603
604 def tag_deleted(contents)
605 tag("-", "diff-deleted-tag", contents)
606 end
607
608 def tag_inserted(contents)
609 tag("+", "diff-inserted-tag", contents)
610 end
611
612 def tag_equal(contents)
613 tag(" ", "normal", contents)
614 end
615
616 def tag_difference(contents)
617 tag("?", "diff-difference-tag", contents)
618 end
619
620 def diff_line(from_line, to_line)
621 to_operations = []
622 from_line, to_line, _operations = line_operations(from_line, to_line)
623
624 no_replace = true
625 _operations.each do |tag,|
626 if tag == :replace
627 no_replace = false
628 break
629 end
630 end
631
632 output_single("?", color("diff-difference-tag"))
633 output_single(" ")
634 _operations.each do |tag, from_start, from_end, to_start, to_end|
635 from_width = compute_width(from_line, from_start, from_end)
636 to_width = compute_width(to_line, to_start, to_end)
637 case tag
638 when :replace
639 output_single(from_line[from_start...from_end],
640 color("diff-deleted"))
641 if (from_width < to_width)
642 output_single(" " * (to_width - from_width))
643 end
644 to_operations << Proc.new do
645 output_single(to_line[to_start...to_end],
646 color("diff-inserted"))
647 if (to_width < from_width)
648 output_single(" " * (from_width - to_width))
649 end
650 end
651 when :delete
652 output_single(from_line[from_start...from_end],
653 color("diff-deleted"))
654 unless no_replace
655 to_operations << Proc.new {output_single(" " * from_width)}
656 end
657 when :insert
658 if no_replace
659 output_single(to_line[to_start...to_end],
660 color("diff-inserted"))
661 else
662 output_single(" " * to_width)
663 to_operations << Proc.new do
664 output_single(to_line[to_start...to_end],
665 color("diff-inserted"))
666 end
667 end
668 when :equal
669 output_single(from_line[from_start...from_end])
670 unless no_replace
671 to_operations << Proc.new {output_single(" " * to_width)}
672 end
673 else
674 raise "unknown tag: #{tag}"
675 end
676 end
677 output("")
678
679 unless to_operations.empty?
680 output_single("?", color("diff-difference-tag"))
681 output_single(" ")
682 to_operations.each do |operation|
683 operation.call
684 end
685 output("")
686 end
687 end
688 end
689 end
690 end
691 end
692 end
+0
-49
vendor/test/unit/ui/emacs/testrunner.rb less more
0 require 'test/unit/ui/console/testrunner'
1
2 module Test
3 module Unit
4 module UI
5 module Emacs
6 class TestRunner < Console::TestRunner
7 private
8 def output_setup_end
9 end
10
11 def output_started
12 end
13
14 def format_fault(fault)
15 return super unless fault.respond_to?(:label)
16 format_method_name = "format_fault_#{fault.label.downcase}"
17 if respond_to?(format_method_name, true)
18 __send__(format_method_name, fault)
19 else
20 super
21 end
22 end
23
24 def format_fault_failure(failure)
25 if failure.location.size == 1
26 location = failure.location[0]
27 location_display = location.sub(/\A(.+:\d+).*/, ' [\\1]')
28 else
29 location_display = "\n" + failure.location.join("\n")
30 end
31 result = "#{failure.label}:\n"
32 result << "#{failure.test_name}#{location_display}:\n"
33 result << failure.message
34 result
35 end
36
37 def format_fault_error(error)
38 result = "#{error.label}:\n"
39 result << "#{error.test_name}:\n"
40 result << "#{error.message}\n"
41 result << error.backtrace.join("\n")
42 result
43 end
44 end
45 end
46 end
47 end
48 end
+0
-53
vendor/test/unit/ui/testrunner.rb less more
0 require 'test/unit/ui/testrunnerutilities'
1
2 module Test
3 module Unit
4 module UI
5 class TestRunner
6 extend TestRunnerUtilities
7
8 attr_reader :listeners
9 def initialize(suite, options={})
10 if suite.respond_to?(:suite)
11 @suite = suite.suite
12 else
13 @suite = suite
14 end
15 @options = options
16 @listeners = @options[:listeners] || []
17 end
18
19 # Begins the test run.
20 def start
21 setup_mediator
22 attach_to_mediator
23 attach_listeners
24 start_mediator
25 end
26
27 private
28 def setup_mediator
29 @mediator = TestRunnerMediator.new(@suite)
30 end
31
32 def attach_listeners
33 @listeners.each do |listener|
34 listener.attach_to_mediator(@mediator)
35 end
36 end
37
38 def start_mediator
39 @mediator.run
40 end
41
42 def diff_target_string?(string)
43 Assertions::AssertionMessage.diff_target_string?(string)
44 end
45
46 def prepare_for_diff(from, to)
47 Assertions::AssertionMessage.prepare_for_diff(from, to)
48 end
49 end
50 end
51 end
52 end
+0
-112
vendor/test/unit/ui/testrunnermediator.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 require 'test/unit'
7 require 'test/unit/util/observable'
8 require 'test/unit/testresult'
9
10 module Test
11 module Unit
12 module UI
13
14 # Provides an interface to write any given UI against,
15 # hopefully making it easy to write new UIs.
16 class TestRunnerMediator
17 RESET = name + "::RESET"
18 STARTED = name + "::STARTED"
19 FINISHED = name + "::FINISHED"
20
21 include Util::Observable
22
23 # Creates a new TestRunnerMediator initialized to run
24 # the passed suite.
25 def initialize(suite)
26 @suite = suite
27 end
28
29 # Runs the suite the TestRunnerMediator was created
30 # with.
31 def run
32 AutoRunner.need_auto_run = false
33
34 result = create_result
35
36 Test::Unit.run_at_start_hooks
37 start_time = Time.now
38 begin
39 with_listener(result) do
40 notify_listeners(RESET, @suite.size)
41 notify_listeners(STARTED, result)
42
43 run_suite(result)
44 end
45 ensure
46 elapsed_time = Time.now - start_time
47 notify_listeners(FINISHED, elapsed_time)
48 end
49 Test::Unit.run_at_exit_hooks
50
51 result
52 end
53
54 # Just for backward compatibility for NetBeans.
55 # NetBeans should not use monkey patching. NetBeans
56 # should use runner change public API.
57 #
58 # See GitHub#38
59 # https://github.com/test-unit/test-unit/issues/38
60 def run_suite(result=nil)
61 if result.nil?
62 run
63 else
64 @suite.run(result) do |channel, value|
65 notify_listeners(channel, value)
66 end
67 end
68 end
69
70 private
71 # A factory method to create the result the mediator
72 # should run with. Can be overridden by subclasses if
73 # one wants to use a different result.
74 def create_result
75 TestResult.new
76 end
77
78 def measure_time
79 begin_time = Time.now
80 yield
81 Time.now - begin_time
82 end
83
84 def with_listener(result)
85 finished_listener = result.add_listener(TestResult::FINISHED) do |*args|
86 notify_listeners(TestResult::FINISHED, *args)
87 end
88 changed_listener = result.add_listener(TestResult::CHANGED) do |*args|
89 notify_listeners(TestResult::CHANGED, *args)
90 end
91 pass_assertion_listener = result.add_listener(TestResult::PASS_ASSERTION) do |*args|
92 notify_listeners(TestResult::PASS_ASSERTION, *args)
93 end
94 fault_listener = result.add_listener(TestResult::FAULT) do |*args|
95 notify_listeners(TestResult::FAULT, *args)
96 end
97
98 begin
99 yield
100 ensure
101 result.remove_listener(TestResult::FAULT, fault_listener)
102 result.remove_listener(TestResult::CHANGED, changed_listener)
103 result.remove_listener(TestResult::FINISHED, finished_listener)
104 result.remove_listener(TestResult::PASS_ASSERTION,
105 pass_assertion_listener)
106 end
107 end
108 end
109 end
110 end
111 end
+0
-41
vendor/test/unit/ui/testrunnerutilities.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 module Test
7 module Unit
8 module UI
9
10 # Provides some utilities common to most, if not all,
11 # TestRunners.
12 #
13 #--
14 #
15 # Perhaps there ought to be a TestRunner superclass? There
16 # seems to be a decent amount of shared code between test
17 # runners.
18
19 module TestRunnerUtilities
20
21 # Creates a new TestRunner and runs the suite.
22 def run(suite, options={})
23 return new(suite, options).start
24 end
25
26 # Takes care of the ARGV parsing and suite
27 # determination necessary for running one of the
28 # TestRunners from the command line.
29 def start_command_line_test
30 if ARGV.empty?
31 puts "You should supply the name of a test suite file to the runner"
32 exit
33 end
34 require ARGV[0].gsub(/.+::/, '')
35 new(eval(ARGV[0])).start
36 end
37 end
38 end
39 end
40 end
+0
-224
vendor/test/unit/ui/xml/testrunner.rb less more
0 #--
1 #
2 # Author:: Kouhei Sutou
3 # Copyright::
4 # * Copyright (c) 2011 Kouhei Sutou <kou@clear-code.com>
5 # License:: Ruby license.
6
7 require 'erb'
8 require 'time'
9 require 'test/unit/ui/testrunner'
10 require 'test/unit/ui/testrunnermediator'
11
12 module Test
13 module Unit
14 module UI
15 module XML
16
17 # Runs a Test::Unit::TestSuite and outputs XML.
18 class TestRunner < UI::TestRunner
19 include ERB::Util
20
21 # Creates a new TestRunner for running the passed
22 # suite. :output option specifies where runner
23 # output should go to; defaults to STDOUT.
24 def initialize(suite, options={})
25 super
26 @output = @options[:output] || STDOUT
27 if @options[:output_file_descriptor]
28 @output = IO.new(@options[:output_file_descriptor], "w")
29 end
30 @already_outputted = false
31 @indent = 0
32 @top_level = true
33 @current_test = nil
34 @current_test_suite = nil
35 @already_outputted = false
36 end
37
38 private
39 def attach_to_mediator
40 @mediator.add_listener(TestResult::PASS_ASSERTION,
41 &method(:result_pass_assertion))
42 @mediator.add_listener(TestResult::FAULT,
43 &method(:result_fault))
44 @mediator.add_listener(TestRunnerMediator::STARTED,
45 &method(:started))
46 @mediator.add_listener(TestRunnerMediator::FINISHED,
47 &method(:finished))
48 @mediator.add_listener(TestCase::STARTED_OBJECT,
49 &method(:test_started))
50 @mediator.add_listener(TestCase::FINISHED_OBJECT,
51 &method(:test_finished))
52 @mediator.add_listener(TestSuite::STARTED_OBJECT,
53 &method(:test_suite_started))
54 @mediator.add_listener(TestSuite::FINISHED_OBJECT,
55 &method(:test_suite_finished))
56 end
57
58 def result_pass_assertion(result)
59 open_tag("pass-assertion") do
60 output_test(@current_test)
61 end
62 end
63
64 def result_fault(fault)
65 open_tag("test-result") do
66 open_tag("result") do
67 output_test_suite(@current_test_suite)
68 output_test(@current_test)
69 open_tag("backtrace") do
70 fault.location.each do |entry|
71 file, line, info = entry.split(/:/, 3)
72 open_tag("entry") do
73 add_content("file", file)
74 add_content("line", line)
75 add_content("info", info)
76 end
77 end
78 end
79 if fault.respond_to?(:expected)
80 add_content("expected", fault.expected)
81 end
82 if fault.respond_to?(:actual)
83 add_content("actual", fault.actual)
84 end
85 add_content("detail", fault.message)
86 add_content("status", fault.label.downcase)
87 end
88 end
89 @already_outputted = true if fault.critical?
90 end
91
92 def started(result)
93 @result = result
94 output_started
95 end
96
97 def output_started
98 open_tag("stream")
99 end
100
101 def finished(elapsed_time)
102 add_content("success", @result.passed?)
103 close_tag("stream")
104 end
105
106 def test_started(test)
107 @already_outputted = false
108 @current_test = test
109 open_tag("start-test") do
110 output_test(test)
111 end
112 end
113
114 def test_finished(test)
115 unless @already_outputted
116 open_tag("test-result") do
117 output_test(test)
118 open_tag("result") do
119 output_test_suite(@current_test_suite)
120 output_test(test)
121 add_content("status", "success")
122 end
123 end
124 end
125
126 open_tag("complete-test") do
127 output_test(test)
128 add_content("success", test.passed?)
129 end
130 @current_test = nil
131 end
132
133 def test_suite_started(suite)
134 @current_test_suite = suite
135 if suite.test_case.nil?
136 open_tag("ready-test-suite") do
137 add_content("n-tests", suite.size)
138 end
139 open_tag("start-test-suite") do
140 output_test_suite(suite)
141 end
142 else
143 open_tag("ready-test-case") do
144 output_test_suite(suite)
145 add_content("n-tests", suite.size)
146 end
147 open_tag("start-test-case") do
148 output_test_suite(suite)
149 end
150 end
151 end
152
153 def test_suite_finished(suite)
154 if suite.test_case.nil?
155 open_tag("complete-test-suite") do
156 output_test_suite(suite)
157 add_content("success", suite.passed?)
158 end
159 else
160 open_tag("complete-test-case") do
161 output_test_suite(suite)
162 add_content("success", suite.passed?)
163 end
164 end
165 @current_test_suite = nil
166 end
167
168 def indent
169 " " * @indent
170 end
171
172 def open_tag(name)
173 @output.puts("#{indent}<#{name}>")
174 @indent += 2
175 if block_given?
176 yield
177 close_tag(name)
178 end
179 end
180
181 def add_content(name, content)
182 return if content.nil?
183 case content
184 when Time
185 content = content.iso8601
186 end
187 @output.puts("#{indent}<#{name}>#{h(content)}</#{name}>")
188 end
189
190 def close_tag(name)
191 @indent -= 2
192 @output.puts("#{indent}</#{name}>")
193 end
194
195 def output_test(test)
196 open_tag("test") do
197 add_content("name", test.method_name)
198 add_content("start-time", test.start_time)
199 add_content("elapsed", test.elapsed_time)
200 end
201 end
202
203 def output_test_suite(test_suite)
204 test_case = test_suite.test_case
205 if test_case.nil?
206 open_tag("test-suite") do
207 add_content("name", test_suite.name)
208 add_content("start-time", test_suite.start_time)
209 add_content("elapsed", test_suite.elapsed_time)
210 end
211 else
212 open_tag("test-case") do
213 add_content("name", test_suite.name)
214 add_content("start-time", test_suite.start_time)
215 add_content("elapsed", test_suite.elapsed_time)
216 end
217 end
218 end
219 end
220 end
221 end
222 end
223 end
+0
-47
vendor/test/unit/util/backtracefilter.rb less more
0 module Test
1 module Unit
2 module Util
3 module BacktraceFilter
4 TESTUNIT_FILE_SEPARATORS = %r{[\\/:]}
5 TESTUNIT_PREFIX = __FILE__.split(TESTUNIT_FILE_SEPARATORS)[0..-3]
6 TESTUNIT_RB_FILE = /\.rb\Z/
7
8 module_function
9 def filter_backtrace(backtrace, prefix=nil)
10 return ["No backtrace"] unless backtrace
11 return backtrace if ENV["TEST_UNIT_ALL_BACKTRACE"]
12
13 if prefix
14 split_prefix = prefix.split(TESTUNIT_FILE_SEPARATORS)
15 else
16 split_prefix = TESTUNIT_PREFIX
17 end
18 test_unit_internal_p = lambda do |entry|
19 components = entry.split(TESTUNIT_FILE_SEPARATORS)
20 split_entry = components[0, split_prefix.size]
21 next false unless split_entry[0..-2] == split_prefix[0..-2]
22 split_entry[-1].sub(TESTUNIT_RB_FILE, '') == split_prefix[-1]
23 end
24
25 in_user_code = false
26 new_backtrace = backtrace.reverse.reject do |entry|
27 if test_unit_internal_p.call(entry)
28 in_user_code = true
29 true
30 else
31 not in_user_code
32 end
33 end.reverse
34
35 if new_backtrace.empty?
36 new_backtrace = backtrace.reject do |entry|
37 test_unit_internal_p.call(entry)
38 end
39 new_backtrace = backtrace if new_backtrace.empty?
40 end
41 new_backtrace
42 end
43 end
44 end
45 end
46 end
+0
-28
vendor/test/unit/util/method-owner-finder.rb less more
0 module Test
1 module Unit
2 module Util
3 module MethodOwnerFinder
4 module_function
5 def find(object, method_name)
6 method = object.method(method_name)
7 return method.owner if method.respond_to?(:owner)
8
9 if /\((.+?)\)\#/ =~ method.to_s
10 owner_name = $1
11 if /\A#</ =~ owner_name
12 ObjectSpace.each_object(Module) do |mod|
13 return mod if mod.to_s == owner_name
14 end
15 else
16 owner_name.split(/::/).inject(Object) do |parent, name|
17 parent.const_get(name)
18 end
19 end
20 else
21 object.class
22 end
23 end
24 end
25 end
26 end
27 end
+0
-90
vendor/test/unit/util/observable.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 require 'test/unit/util/procwrapper'
7
8 module Test
9 module Unit
10 module Util
11
12 # This is a utility class that allows anything mixing
13 # it in to notify a set of listeners about interesting
14 # events.
15 module Observable
16 # We use this for defaults since nil might mean something
17 NOTHING = "NOTHING/#{__id__}"
18
19 # Adds the passed proc as a listener on the
20 # channel indicated by channel_name. listener_key
21 # is used to remove the listener later; if none is
22 # specified, the proc itself is used.
23 #
24 # Whatever is used as the listener_key is
25 # returned, making it very easy to use the proc
26 # itself as the listener_key:
27 #
28 # listener = add_listener("Channel") { ... }
29 # remove_listener("Channel", listener)
30 def add_listener(channel_name, listener_key=NOTHING, &listener) # :yields: value
31 unless(block_given?)
32 raise ArgumentError.new("No callback was passed as a listener")
33 end
34
35 key = listener_key
36 if (listener_key == NOTHING)
37 listener_key = listener
38 key = ProcWrapper.new(listener)
39 end
40
41 channels[channel_name] ||= {}
42 channels[channel_name][key] = listener
43 return listener_key
44 end
45
46 # Removes the listener indicated by listener_key
47 # from the channel indicated by
48 # channel_name. Returns the registered proc, or
49 # nil if none was found.
50 def remove_listener(channel_name, listener_key)
51 channel = channels[channel_name]
52 return nil unless (channel)
53 key = listener_key
54 if (listener_key.instance_of?(Proc))
55 key = ProcWrapper.new(listener_key)
56 end
57 if (channel.has_key?(key))
58 return channel.delete(key)
59 end
60 return nil
61 end
62
63 # Calls all the procs registered on the channel
64 # indicated by channel_name. If value is
65 # specified, it is passed in to the procs,
66 # otherwise they are called with no arguments.
67 #
68 #--
69 #
70 # Perhaps this should be private? Would it ever
71 # make sense for an external class to call this
72 # method directly?
73 def notify_listeners(channel_name, *arguments)
74 channel = channels[channel_name]
75 return 0 unless (channel)
76 listeners = channel.values
77 listeners.each { |listener| listener.call(*arguments) }
78 return listeners.size
79 end
80
81 private
82 def channels
83 @channels ||= {}
84 return @channels
85 end
86 end
87 end
88 end
89 end
+0
-31
vendor/test/unit/util/output.rb less more
0 module Test
1 module Unit
2 module Util
3 module Output
4 ##
5 # Returns output for standard output and standard
6 # error as string.
7 #
8 # Example:
9 # capture_output do
10 # puts("stdout")
11 # warn("stderr")
12 # end # -> ["stdout\n", "stderr\n"]
13 def capture_output
14 require 'stringio'
15
16 output = StringIO.new
17 error = StringIO.new
18 stdout_save, stderr_save = $stdout, $stderr
19 $stdout, $stderr = output, error
20 begin
21 yield
22 [output.string, error.string]
23 ensure
24 $stdout, $stderr = stdout_save, stderr_save
25 end
26 end
27 end
28 end
29 end
30 end
+0
-48
vendor/test/unit/util/procwrapper.rb less more
0 #--
1 #
2 # Author:: Nathaniel Talbott.
3 # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4 # License:: Ruby license.
5
6 module Test
7 module Unit
8 module Util
9
10 # Allows the storage of a Proc passed through '&' in a
11 # hash.
12 #
13 # Note: this may be inefficient, since the hash being
14 # used is not necessarily very good. In Observable,
15 # efficiency is not too important, since the hash is
16 # only accessed when adding and removing listeners,
17 # not when notifying.
18
19 class ProcWrapper
20
21 # Creates a new wrapper for a_proc.
22 def initialize(a_proc)
23 @a_proc = a_proc
24 @hash = a_proc.inspect.sub(/^(#<#{a_proc.class}:)/){''}.sub(/(>)$/){''}.hex
25 end
26
27 def hash
28 return @hash
29 end
30
31 def ==(other)
32 case(other)
33 when ProcWrapper
34 return @a_proc == other.to_proc
35 else
36 return super
37 end
38 end
39 alias :eql? :==
40
41 def to_proc
42 return @a_proc
43 end
44 end
45 end
46 end
47 end
+0
-5
vendor/test/unit/version.rb less more
0 module Test
1 module Unit
2 VERSION = '3.1.2'
3 end
4 end
+0
-505
vendor/test/unit.rb less more
0 require 'test/unit/testcase'
1 require 'test/unit/autorunner'
2
3 module Test # :nodoc:
4 #
5 # = Test::Unit - Ruby Unit Testing Framework
6 #
7 # == Introduction
8 #
9 # Unit testing is making waves all over the place, largely due to the
10 # fact that it is a core practice of XP. While XP is great, unit testing
11 # has been around for a long time and has always been a good idea. One
12 # of the keys to good unit testing, though, is not just writing tests,
13 # but having tests. What's the difference? Well, if you just _write_ a
14 # test and throw it away, you have no guarantee that something won't
15 # change later which breaks your code. If, on the other hand, you _have_
16 # tests (obviously you have to write them first), and run them as often
17 # as possible, you slowly build up a wall of things that cannot break
18 # without you immediately knowing about it. This is when unit testing
19 # hits its peak usefulness.
20 #
21 # Enter Test::Unit, a framework for unit testing in Ruby, helping you to
22 # design, debug and evaluate your code by making it easy to write and
23 # have tests for it.
24 #
25 #
26 # == Notes
27 #
28 # Test::Unit has grown out of and superceded Lapidary.
29 #
30 #
31 # == Feedback
32 #
33 # I like (and do my best to practice) XP, so I value early releases,
34 # user feedback, and clean, simple, expressive code. There is always
35 # room for improvement in everything I do, and Test::Unit is no
36 # exception. Please, let me know what you think of Test::Unit as it
37 # stands, and what you'd like to see expanded/changed/improved/etc. If
38 # you find a bug, let me know ASAP; one good way to let me know what the
39 # bug is is to submit a new test that catches it :-) Also, I'd love to
40 # hear about any successes you have with Test::Unit, and any
41 # documentation you might add will be greatly appreciated. My contact
42 # info is below.
43 #
44 #
45 # == Contact Information
46 #
47 # A lot of discussion happens about Ruby in general on the ruby-talk
48 # mailing list (http://www.ruby-lang.org/en/ml.html), and you can ask
49 # any questions you might have there. I monitor the list, as do many
50 # other helpful Rubyists, and you're sure to get a quick answer. Of
51 # course, you're also welcome to email me (Nathaniel Talbott) directly
52 # at mailto:testunit@talbott.ws, and I'll do my best to help you out.
53 #
54 #
55 # == Credits
56 #
57 # I'd like to thank...
58 #
59 # Matz, for a great language!
60 #
61 # Masaki Suketa, for his work on RubyUnit, which filled a vital need in
62 # the Ruby world for a very long time. I'm also grateful for his help in
63 # polishing Test::Unit and getting the RubyUnit compatibility layer
64 # right. His graciousness in allowing Test::Unit to supercede RubyUnit
65 # continues to be a challenge to me to be more willing to defer my own
66 # rights.
67 #
68 # Ken McKinlay, for his interest and work on unit testing, and for his
69 # willingness to dialog about it. He was also a great help in pointing
70 # out some of the holes in the RubyUnit compatibility layer.
71 #
72 # Dave Thomas, for the original idea that led to the extremely simple
73 # "require 'test/unit'", plus his code to improve it even more by
74 # allowing the selection of tests from the command-line. Also, without
75 # RDoc, the documentation for Test::Unit would stink a lot more than it
76 # does now.
77 #
78 # Everyone who's helped out with bug reports, feature ideas,
79 # encouragement to continue, etc. It's a real privilege to be a part of
80 # the Ruby community.
81 #
82 # The guys at RoleModel Software, for putting up with me repeating, "But
83 # this would be so much easier in Ruby!" whenever we're coding in Java.
84 #
85 # My Creator, for giving me life, and giving it more abundantly.
86 #
87 #
88 # == License
89 #
90 # Test::Unit is copyright (c) 2000-2003 Nathaniel Talbott. It is free
91 # software, and is distributed under the Ruby license. See the COPYING
92 # file.
93 #
94 # Exception: lib/test/unit/diff.rb is copyright (c)
95 # 2008-2010 Kouhei Sutou and 2001-2008 Python Software
96 # Foundation. It is free software, and is distributed
97 # under the Ruby license and/or the PSF license. See the
98 # COPYING file and PSFL file.
99 #
100 # == Warranty
101 #
102 # This software is provided "as is" and without any express or
103 # implied warranties, including, without limitation, the implied
104 # warranties of merchantibility and fitness for a particular
105 # purpose.
106 #
107 #
108 # == Author
109 #
110 # Nathaniel Talbott.
111 # Copyright (c) 2000-2003, Nathaniel Talbott
112 #
113 # ----
114 #
115 # = Usage
116 #
117 # The general idea behind unit testing is that you write a _test_
118 # _method_ that makes certain _assertions_ about your code, working
119 # against a _test_ _fixture_. A bunch of these _test_ _methods_ are
120 # bundled up into a _test_ _suite_ and can be run any time the
121 # developer wants. The results of a run are gathered in a _test_
122 # _result_ and displayed to the user through some UI. So, lets break
123 # this down and see how Test::Unit provides each of these necessary
124 # pieces.
125 #
126 #
127 # == Assertions
128 #
129 # These are the heart of the framework. Think of an assertion as a
130 # statement of expected outcome, i.e. "I assert that x should be equal
131 # to y". If, when the assertion is executed, it turns out to be
132 # correct, nothing happens, and life is good. If, on the other hand,
133 # your assertion turns out to be false, an error is propagated with
134 # pertinent information so that you can go back and make your
135 # assertion succeed, and, once again, life is good. For an explanation
136 # of the current assertions, see Test::Unit::Assertions.
137 #
138 #
139 # == Test Method & Test Fixture
140 #
141 # Obviously, these assertions have to be called within a context that
142 # knows about them and can do something meaningful with their
143 # pass/fail value. Also, it's handy to collect a bunch of related
144 # tests, each test represented by a method, into a common test class
145 # that knows how to run them. The tests will be in a separate class
146 # from the code they're testing for a couple of reasons. First of all,
147 # it allows your code to stay uncluttered with test code, making it
148 # easier to maintain. Second, it allows the tests to be stripped out
149 # for deployment, since they're really there for you, the developer,
150 # and your users don't need them. Third, and most importantly, it
151 # allows you to set up a common test fixture for your tests to run
152 # against.
153 #
154 # What's a test fixture? Well, tests do not live in a vacuum; rather,
155 # they're run against the code they are testing. Often, a collection
156 # of tests will run against a common set of data, also called a
157 # fixture. If they're all bundled into the same test class, they can
158 # all share the setting up and tearing down of that data, eliminating
159 # unnecessary duplication and making it much easier to add related
160 # tests.
161 #
162 # Test::Unit::TestCase wraps up a collection of test methods together
163 # and allows you to easily set up and tear down the same test fixture
164 # for each test. This is done by overriding #setup and/or #teardown,
165 # which will be called before and after each test method that is
166 # run. The TestCase also knows how to collect the results of your
167 # assertions into a Test::Unit::TestResult, which can then be reported
168 # back to you... but I'm getting ahead of myself. To write a test,
169 # follow these steps:
170 #
171 # * Make sure Test::Unit is in your library path.
172 # * require 'test/unit' in your test script.
173 # * Create a class that subclasses Test::Unit::TestCase.
174 # * Add a method that begins with "test" to your class.
175 # * Make assertions in your test method.
176 # * Optionally define #setup and/or #teardown to set up and/or tear
177 # down your common test fixture.
178 # * You can now run your test as you would any other Ruby
179 # script... try it and see!
180 #
181 # A really simple test might look like this (#setup and #teardown are
182 # commented out to indicate that they are completely optional):
183 #
184 # require 'test/unit'
185 #
186 # class MyTest < Test::Unit::TestCase
187 # # def setup
188 # # end
189 #
190 # # def teardown
191 # # end
192 #
193 # def test_fail
194 # assert(false, 'Assertion was false.')
195 # end
196 # end
197 #
198 #
199 # == Test Runners
200 #
201 # So, now you have this great test class, but you still
202 # need a way to run it and view any failures that occur
203 # during the run. There are some test runner; console test
204 # runner, GTK+ test runner and so on. The console test
205 # runner is automatically invoked for you if you require
206 # 'test/unit' and simply run the file. To use another
207 # runner simply set default test runner ID to
208 # Test::Unit::AutoRunner:
209 #
210 # require 'test/unit'
211 # Test::Unit::AutoRunner.default_runner = "gtk2"
212 #
213 # == Test Suite
214 #
215 # As more and more unit tests accumulate for a given project, it
216 # becomes a real drag running them one at a time, and it also
217 # introduces the potential to overlook a failing test because you
218 # forget to run it. Suddenly it becomes very handy that the
219 # TestRunners can take any object that returns a Test::Unit::TestSuite
220 # in response to a suite method. The TestSuite can, in turn, contain
221 # other TestSuites or individual tests (typically created by a
222 # TestCase). In other words, you can easily wrap up a group of
223 # TestCases and TestSuites.
224 #
225 # Test::Unit does a little bit more for you, by wrapping
226 # these up automatically when you require
227 # 'test/unit'. What does this mean? It means you could
228 # write the above test case like this instead:
229 #
230 # require 'test/unit'
231 # require 'test_myfirsttests'
232 # require 'test_moretestsbyme'
233 # require 'test_anothersetoftests'
234 #
235 # Test::Unit is smart enough to find all the test cases existing in
236 # the ObjectSpace and wrap them up into a suite for you. It then runs
237 # the dynamic suite using the console TestRunner.
238 #
239 #
240 # == Configuration file
241 #
242 # Test::Unit reads 'test-unit.yml' in the current working
243 # directory as Test::Unit's configuration file. It can
244 # contain the following configurations:
245 #
246 # * color scheme definitions
247 # * test runner to be used
248 # * test runner options
249 # * test collector to be used
250 #
251 # Except color scheme definitions, all of them are
252 # specified by command line option.
253 #
254 # Here are sample color scheme definitions:
255 #
256 # color_schemes:
257 # inverted:
258 # success:
259 # name: red
260 # bold: true
261 # failure:
262 # name: green
263 # bold: true
264 # other_scheme:
265 # ...
266 #
267 # Here are the syntax of color scheme definitions:
268 #
269 # color_schemes:
270 # SCHEME_NAME:
271 # EVENT_NAME:
272 # name: COLOR_NAME
273 # intensity: BOOLEAN
274 # bold: BOOLEAN
275 # italic: BOOLEAN
276 # underline: BOOLEAN
277 # ...
278 # ...
279 #
280 # SCHEME_NAME:: the name of the color scheme
281 # EVENT_NAME:: one of [success, failure, pending,
282 # omission, notification, error]
283 # COLOR_NAME:: one of [black, red, green, yellow, blue,
284 # magenta, cyan, white]
285 # BOOLEAN:: true or false
286 #
287 # You can use the above 'inverted' color scheme with the
288 # following configuration:
289 #
290 # runner: console
291 # console_options:
292 # color_scheme: inverted
293 # color_schemes:
294 # inverted:
295 # success:
296 # name: red
297 # bold: true
298 # failure:
299 # name: green
300 # bold: true
301 #
302 # == Questions?
303 #
304 # I'd really like to get feedback from all levels of Ruby
305 # practitioners about typos, grammatical errors, unclear statements,
306 # missing points, etc., in this document (or any other).
307 #
308
309 module Unit
310 class << self
311 # Set true when Test::Unit has run. If set to true Test::Unit
312 # will not automatically run at exit.
313 #
314 # @deprecated Use Test::Unit::AutoRunner.need_auto_run= instead.
315 def run=(have_run)
316 AutoRunner.need_auto_run = (not have_run)
317 end
318
319 # Already tests have run?
320 #
321 # @deprecated Use Test::Unit::AutoRunner.need_auto_run? instead.
322 def run?
323 not AutoRunner.need_auto_run?
324 end
325
326 # @api private
327 @@at_start_hooks = []
328
329 # Regsiter a hook that is run before running tests.
330 # To register multiple hooks, call this method multiple times.
331 #
332 # Here is an example test case:
333 # Test::Unit.at_start do
334 # # ...
335 # end
336 #
337 # class TestMyClass1 < Test::Unit::TestCase
338 # class << self
339 # def startup
340 # # ...
341 # end
342 # end
343 #
344 # def setup
345 # # ...
346 # end
347 #
348 # def test_my_class1
349 # # ...
350 # end
351 #
352 # def test_my_class2
353 # # ...
354 # end
355 # end
356 #
357 # class TestMyClass2 < Test::Unit::TestCase
358 # class << self
359 # def startup
360 # # ...
361 # end
362 # end
363 #
364 # def setup
365 # # ...
366 # end
367 #
368 # def test_my_class1
369 # # ...
370 # end
371 #
372 # def test_my_class2
373 # # ...
374 # end
375 # end
376 #
377 # Here is a call order:
378 # * at_start
379 # * TestMyClass1.startup
380 # * TestMyClass1#setup
381 # * TestMyClass1#test_my_class1
382 # * TestMyClass1#setup
383 # * TestMyClass1#test_my_class2
384 # * TestMyClass2#setup
385 # * TestMyClass2#test_my_class1
386 # * TestMyClass2#setup
387 # * TestMyClass2#test_my_class2
388 #
389 # @example
390 # Test::Unit.at_start do
391 # puts "Start!"
392 # end
393 #
394 # @yield A block that is run before running tests.
395 # @yieldreturn [void]
396 # @return [void]
397 #
398 # @since 2.5.2
399 def at_start(&hook)
400 @@at_start_hooks << hook
401 end
402
403 # @api private
404 def run_at_start_hooks
405 @@at_start_hooks.each do |hook|
406 hook.call
407 end
408 end
409
410 # @api private
411 @@at_exit_hooks = []
412
413 # Regsiter a hook that is run after running tests.
414 # To register multiple hooks, call this method multiple times.
415 #
416 # Here is an example test case:
417 # Test::Unit.at_exit do
418 # # ...
419 # end
420 #
421 # class TestMyClass1 < Test::Unit::TestCase
422 # class << self
423 # def shutdown
424 # # ...
425 # end
426 # end
427 #
428 # def teardown
429 # # ...
430 # end
431 #
432 # def test_my_class1
433 # # ...
434 # end
435 #
436 # def test_my_class2
437 # # ...
438 # end
439 # end
440 #
441 # class TestMyClass2 < Test::Unit::TestCase
442 # class << self
443 # def shutdown
444 # # ...
445 # end
446 # end
447 #
448 # def teardown
449 # # ...
450 # end
451 #
452 # def test_my_class1
453 # # ...
454 # end
455 #
456 # def test_my_class2
457 # # ...
458 # end
459 # end
460 #
461 # Here is a call order:
462 # * TestMyClass1#test_my_class1
463 # * TestMyClass1#teardown
464 # * TestMyClass1#test_my_class2
465 # * TestMyClass1#teardown
466 # * TestMyClass1.shutdown
467 # * TestMyClass2#test_my_class1
468 # * TestMyClass2#teardown
469 # * TestMyClass2#test_my_class2
470 # * TestMyClass2#teardown
471 # * TestMyClass2.shutdown
472 # * at_exit
473 #
474 # @example
475 # Test::Unit.at_exit do
476 # puts "Exit!"
477 # end
478 #
479 # @yield A block that is run after running tests.
480 # @yieldreturn [void]
481 # @return [void]
482 #
483 # @since 2.5.2
484 def at_exit(&hook)
485 @@at_exit_hooks << hook
486 end
487
488 # @api private
489 def run_at_exit_hooks
490 @@at_exit_hooks.each do |hook|
491 hook.call
492 end
493 end
494 end
495 end
496 end
497
498 Module.new do
499 at_exit do
500 if $!.nil? and Test::Unit::AutoRunner.need_auto_run?
501 exit Test::Unit::AutoRunner.run
502 end
503 end
504 end
+0
-32
vendor/test-unit.rb less more
0 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
1 #
2 # License: Ruby's or LGPLv2.1 or later
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 # 02110-1301 USA
18
19 module Test
20 module Unit
21 autoload :TestCase, "test/unit/testcase"
22 autoload :AutoRunner, "test/unit/autorunner"
23 end
24 end
25
26 # experimental. It is for "ruby -rtest-unit -e run test/test_*.rb".
27 # Is this API OK or dirty?
28 def run
29 self.class.send(:undef_method, :run)
30 require "test/unit"
31 end
+0
-357
vendor/text/double_metaphone.rb less more
0 # encoding: utf-8
1 #
2 # Ruby implementation of the Double Metaphone algorithm by Lawrence Philips,
3 # originally published in the June 2000 issue of C/C++ Users Journal.
4 #
5 # Based on Stephen Woodbridge's PHP version - http://swoodbridge.com/DoubleMetaPhone/
6 #
7 # Author: Tim Fletcher (mail@tfletcher.com)
8 #
9
10 module Text # :nodoc:
11 module Metaphone
12
13 # Returns the primary and secondary double metaphone tokens
14 # (the secondary will be nil if equal to the primary).
15 def double_metaphone(str)
16 primary, secondary, current = [], [], 0
17 original, length, last = "#{str} ".upcase, str.length, str.length - 1
18 if /^GN|KN|PN|WR|PS$/ =~ original[0, 2]
19 current += 1
20 end
21 if 'X' == original[0, 1]
22 primary << :S
23 secondary << :S
24 current += 1
25 end
26 while primary.length < 4 || secondary.length < 4
27 break if current > str.length
28 a, b, c = double_metaphone_lookup(original, current, length, last)
29 primary << a if a
30 secondary << b if b
31 current += c if c
32 end
33 primary, secondary = primary.join("")[0, 4], secondary.join("")[0, 4]
34 return primary, (primary == secondary ? nil : secondary)
35 end
36
37
38 private
39
40 def slavo_germanic?(str)
41 /W|K|CZ|WITZ/ =~ str
42 end
43
44 def vowel?(str)
45 /^A|E|I|O|U|Y$/ =~ str
46 end
47
48 def double_metaphone_lookup(str, pos, length, last)
49 case str[pos, 1]
50 when /^A|E|I|O|U|Y$/
51 if 0 == pos
52 return :A, :A, 1
53 else
54 return nil, nil, 1
55 end
56 when 'B'
57 return :P, :P, ('B' == str[pos + 1, 1] ? 2 : 1)
58 when 'Ç'
59 return :S, :S, 1
60 when 'C'
61 if pos > 1 &&
62 !vowel?(str[pos - 2, 1]) &&
63 'ACH' == str[pos - 1, 3] &&
64 str[pos + 2, 1] != 'I' && (
65 str[pos + 2, 1] != 'E' ||
66 str[pos - 2, 6] =~ /^(B|M)ACHER$/
67 ) then
68 return :K, :K, 2
69 elsif 0 == pos && 'CAESAR' == str[pos, 6]
70 return :S, :S, 2
71 elsif 'CHIA' == str[pos, 4]
72 return :K, :K, 2
73 elsif 'CH' == str[pos, 2]
74 if pos > 0 && 'CHAE' == str[pos, 4]
75 return :K, :X, 2
76 elsif 0 == pos && (
77 ['HARAC', 'HARIS'].include?(str[pos + 1, 5]) ||
78 ['HOR', 'HYM', 'HIA', 'HEM'].include?(str[pos + 1, 3])
79 ) && str[0, 5] != 'CHORE' then
80 return :K, :K, 2
81 elsif ['VAN ','VON '].include?(str[0, 4]) ||
82 'SCH' == str[0, 3] ||
83 ['ORCHES','ARCHIT','ORCHID'].include?(str[pos - 2, 6]) ||
84 ['T','S'].include?(str[pos + 2, 1]) || (
85 ((0 == pos) || ['A','O','U','E'].include?(str[pos - 1, 1])) &&
86 ['L','R','N','M','B','H','F','V','W',' '].include?(str[pos + 2, 1])
87 ) then
88 return :K, :K, 2
89 elsif pos > 0
90 return ('MC' == str[0, 2] ? 'K' : 'X'), 'K', 2
91 else
92 return :X, :X, 2
93 end
94 elsif 'CZ' == str[pos, 2] && 'WICZ' != str[pos - 2, 4]
95 return :S, :X, 2
96 elsif 'CIA' == str[pos + 1, 3]
97 return :X, :X, 3
98 elsif 'CC' == str[pos, 2] && !(1 == pos && 'M' == str[0, 1])
99 if /^I|E|H$/ =~ str[pos + 2, 1] && 'HU' != str[pos + 2, 2]
100 if (1 == pos && 'A' == str[pos - 1, 1]) ||
101 /^UCCE(E|S)$/ =~ str[pos - 1, 5] then
102 return :KS, :KS, 3
103 else
104 return :X, :X, 3
105 end
106 else
107 return :K, :K, 2
108 end
109 elsif /^C(K|G|Q)$/ =~ str[pos, 2]
110 return :K, :K, 2
111 elsif /^C(I|E|Y)$/ =~ str[pos, 2]
112 return :S, (/^CI(O|E|A)$/ =~ str[pos, 3] ? :X : :S), 2
113 else
114 if /^ (C|Q|G)$/ =~ str[pos + 1, 2]
115 return :K, :K, 3
116 else
117 return :K, :K, (/^C|K|Q$/ =~ str[pos + 1, 1] && !(['CE','CI'].include?(str[pos + 1, 2])) ? 2 : 1)
118 end
119 end
120 when 'D'
121 if 'DG' == str[pos, 2]
122 if /^I|E|Y$/ =~ str[pos + 2, 1]
123 return :J, :J, 3
124 else
125 return :TK, :TK, 2
126 end
127 else
128 return :T, :T, (/^D(T|D)$/ =~ str[pos, 2] ? 2 : 1)
129 end
130 when 'F'
131 return :F, :F, ('F' == str[pos + 1, 1] ? 2 : 1)
132 when 'G'
133 if 'H' == str[pos + 1, 1]
134 if pos > 0 && !vowel?(str[pos - 1, 1])
135 return :K, :K, 2
136 elsif 0 == pos
137 if 'I' == str[pos + 2, 1]
138 return :J, :J, 2
139 else
140 return :K, :K, 2
141 end
142 elsif (pos > 1 && /^B|H|D$/ =~ str[pos - 2, 1]) ||
143 (pos > 2 && /^B|H|D$/ =~ str[pos - 3, 1]) ||
144 (pos > 3 && /^B|H$/ =~ str[pos - 4, 1])
145 return nil, nil, 2
146 else
147 if (pos > 2 && 'U' == str[pos - 1, 1] && /^C|G|L|R|T$/ =~ str[pos - 3, 1])
148 return :F, :F, 2
149 elsif pos > 0 && 'I' != str[pos - 1, 1]
150 return :K, :K, 2
151 else
152 return nil, nil, 2
153 end
154 end
155 elsif 'N' == str[pos + 1, 1]
156 if 1 == pos && vowel?(str[0, 1]) && !slavo_germanic?(str)
157 return :KN, :N, 2
158 else
159 if 'EY' != str[pos + 2, 2] && 'Y' != str[pos + 1, 1] && !slavo_germanic?(str)
160 return :N, :KN, 2
161 else
162 return :KN, :KN, 2
163 end
164 end
165 elsif 'LI' == str[pos + 1, 2] && !slavo_germanic?(str)
166 return :KL, :L, 2
167 elsif 0 == pos && ('Y' == str[pos + 1, 1] || /^(E(S|P|B|L|Y|I|R)|I(B|L|N|E))$/ =~ str[pos + 1, 2])
168 return :K, :J, 2
169 elsif (('ER' == str[pos + 1, 2] || 'Y' == str[pos + 1, 1]) &&
170 /^(D|R|M)ANGER$/ !~ str[0, 6] &&
171 /^E|I$/ !~ str[pos - 1, 1] &&
172 /^(R|O)GY$/ !~ str[pos - 1, 3])
173 return :K, :J, 2
174 elsif /^E|I|Y$/ =~ str[pos + 1, 1] || /^(A|O)GGI$/ =~ str[pos - 1, 4]
175 if (/^V(A|O)N $/ =~ str[0, 4] || 'SCH' == str[0, 3]) || 'ET' == str[pos + 1, 2]
176 return :K, :K, 2
177 else
178 if 'IER ' == str[pos + 1, 4]
179 return :J, :J, 2
180 else
181 return :J, :K, 2
182 end
183 end
184 elsif 'G' == str[pos + 1, 1]
185 return :K, :K, 2
186 else
187 return :K, :K, 1
188 end
189 when 'H'
190 if (0 == pos || vowel?(str[pos - 1, 1])) && vowel?(str[pos + 1, 1])
191 return :H, :H, 2
192 else
193 return nil, nil, 1
194 end
195 when 'J'
196 if 'JOSE' == str[pos, 4] || 'SAN ' == str[0, 4]
197 if (0 == pos && ' ' == str[pos + 4, 1]) || 'SAN ' == str[0, 4]
198 return :H, :H, 1
199 else
200 return :J, :H, 1
201 end
202 else
203 current = ('J' == str[pos + 1, 1] ? 2 : 1)
204
205 if 0 == pos && 'JOSE' != str[pos, 4]
206 return :J, :A, current
207 else
208 if vowel?(str[pos - 1, 1]) && !slavo_germanic?(str) && /^A|O$/ =~ str[pos + 1, 1]
209 return :J, :H, current
210 else
211 if last == pos
212 return :J, nil, current
213 else
214 if /^L|T|K|S|N|M|B|Z$/ !~ str[pos + 1, 1] && /^S|K|L$/ !~ str[pos - 1, 1]
215 return :J, :J, current
216 else
217 return nil, nil, current
218 end
219 end
220 end
221 end
222 end
223 when 'K'
224 return :K, :K, ('K' == str[pos + 1, 1] ? 2 : 1)
225 when 'L'
226 if 'L' == str[pos + 1, 1]
227 if (((length - 3) == pos && /^(ILL(O|A)|ALLE)$/ =~ str[pos - 1, 4]) ||
228 ((/^(A|O)S$/ =~ str[last - 1, 2] || /^A|O$/ =~ str[last, 1]) && 'ALLE' == str[pos - 1, 4]))
229 return :L, nil, 2
230 else
231 return :L, :L, 2
232 end
233 else
234 return :L, :L, 1
235 end
236 when 'M'
237 if ('UMB' == str[pos - 1, 3] &&
238 ((last - 1) == pos || 'ER' == str[pos + 2, 2])) || 'M' == str[pos + 1, 1]
239 return :M, :M, 2
240 else
241 return :M, :M, 1
242 end
243 when 'N'
244 return :N, :N, ('N' == str[pos + 1, 1] ? 2 : 1)
245 when 'Ñ'
246 return :N, :N, 1
247 when 'P'
248 if 'H' == str[pos + 1, 1]
249 return :F, :F, 2
250 else
251 return :P, :P, (/^P|B$/ =~ str[pos + 1, 1] ? 2 : 1)
252 end
253 when 'Q'
254 return :K, :K, ('Q' == str[pos + 1, 1] ? 2 : 1)
255 when 'R'
256 current = ('R' == str[pos + 1, 1] ? 2 : 1)
257
258 if last == pos && !slavo_germanic?(str) && 'IE' == str[pos - 2, 2] && /^M(E|A)$/ !~ str[pos - 4, 2]
259 return nil, :R, current
260 else
261 return :R, :R, current
262 end
263 when 'S'
264 if /^(I|Y)SL$/ =~ str[pos - 1, 3]
265 return nil, nil, 1
266 elsif 0 == pos && 'SUGAR' == str[pos, 5]
267 return :X, :S, 1
268 elsif 'SH' == str[pos, 2]
269 if /^H(EIM|OEK|OLM|OLZ)$/ =~ str[pos + 1, 4]
270 return :S, :S, 2
271 else
272 return :X, :X, 2
273 end
274 elsif /^SI(O|A)$/ =~ str[pos, 3] || 'SIAN' == str[pos, 4]
275 return :S, (slavo_germanic?(str) ? :S : :X), 3
276 elsif (0 == pos && /^M|N|L|W$/ =~ str[pos + 1, 1]) || 'Z' == str[pos + 1, 1]
277 return :S, :X, ('Z' == str[pos + 1, 1] ? 2 : 1)
278 elsif 'SC' == str[pos, 2]
279 if 'H' == str[pos + 2, 1]
280 if /^OO|ER|EN|UY|ED|EM$/ =~ str[pos + 3, 2]
281 return (/^E(R|N)$/ =~ str[pos + 3, 2] ? :X : :SK), :SK, 3
282 else
283 return :X, ((0 == pos && !vowel?(str[3, 1]) && ('W' != str[pos + 3, 1])) ? :S : :X), 3
284 end
285 elsif /^I|E|Y$/ =~ str[pos + 2, 1]
286 return :S, :S, 3
287 else
288 return :SK, :SK, 3
289 end
290 else
291 return (last == pos && /^(A|O)I$/ =~ str[pos - 2, 2] ? nil : 'S'), 'S', (/^S|Z$/ =~ str[pos + 1, 1] ? 2 : 1)
292 end
293 when 'T'
294 if 'TION' == str[pos, 4]
295 return :X, :X, 3
296 elsif /^T(IA|CH)$/ =~ str[pos, 3]
297 return :X, :X, 3
298 elsif 'TH' == str[pos, 2] || 'TTH' == str[pos, 3]
299 if /^(O|A)M$/ =~ str[pos + 2, 2] || /^V(A|O)N $/ =~ str[0, 4] || 'SCH' == str[0, 3]
300 return :T, :T, 2
301 else
302 return 0, :T, 2
303 end
304 else
305 return :T, :T, (/^T|D$/ =~ str[pos + 1, 1] ? 2 : 1)
306 end
307 when 'V'
308 return :F, :F, ('V' == str[pos + 1, 1] ? 2 : 1)
309 when 'W'
310 if 'WR' == str[pos, 2]
311 return :R, :R, 2
312 end
313 pri, sec = nil, nil
314
315 if 0 == pos && (vowel?(str[pos + 1, 1]) || 'WH' == str[pos, 2])
316 pri = :A
317 sec = vowel?(str[pos + 1, 1]) ? :F : :A
318 end
319
320 if (last == pos && vowel?(str[pos - 1, 1])) || 'SCH' == str[0, 3] ||
321 /^EWSKI|EWSKY|OWSKI|OWSKY$/ =~ str[pos - 1, 5]
322 return pri, "#{sec}F".intern, 1
323 elsif /^WI(C|T)Z$/ =~ str[pos, 4]
324 return "#{pri}TS".intern, "#{sec}FX".intern, 4
325 else
326 return pri, sec, 1
327 end
328 when 'X'
329 current = (/^C|X$/ =~ str[pos + 1, 1] ? 2 : 1)
330
331 if !(last == pos && (/^(I|E)AU$/ =~ str[pos - 3, 3] || /^(A|O)U$/ =~ str[pos - 2, 2]))
332 return :KS, :KS, current
333 else
334 return nil, nil, current
335 end
336 when 'Z'
337 if 'H' == str[pos + 1, 1]
338 return :J, :J, 2
339 else
340 current = ('Z' == str[pos + 1, 1] ? 2 : 1)
341
342 if /^Z(O|I|A)$/ =~ str[pos + 1, 2] || (slavo_germanic?(str) && (pos > 0 && 'T' != str[pos - 1, 1]))
343 return :S, :TS, current
344 else
345 return :S, :S, current
346 end
347 end
348 else
349 return nil, nil, 1
350 end
351 end # def double_metaphone_lookup
352
353 extend self
354
355 end # module Metaphone
356 end # module Text
+0
-159
vendor/text/levenshtein.rb less more
0 #
1 # Levenshtein distance algorithm implementation for Ruby, with UTF-8 support.
2 #
3 # The Levenshtein distance is a measure of how similar two strings s and t are,
4 # calculated as the number of deletions/insertions/substitutions needed to
5 # transform s into t. The greater the distance, the more the strings differ.
6 #
7 # The Levenshtein distance is also sometimes referred to as the
8 # easier-to-pronounce-and-spell 'edit distance'.
9 #
10 # Author: Paul Battley (pbattley@gmail.com)
11 #
12
13 module Text # :nodoc:
14 module Levenshtein
15
16 # Calculate the Levenshtein distance between two strings +str1+ and +str2+.
17 #
18 # The optional argument max_distance can reduce the number of iterations by
19 # stopping if the Levenshtein distance exceeds this value. This increases
20 # performance where it is only necessary to compare the distance with a
21 # reference value instead of calculating the exact distance.
22 #
23 # The distance is calculated in terms of Unicode codepoints. Be aware that
24 # this algorithm does not perform normalisation: if there is a possibility
25 # of different normalised forms being used, normalisation should be performed
26 # beforehand.
27 #
28 def distance(str1, str2, max_distance = nil)
29 if max_distance
30 distance_with_maximum(str1, str2, max_distance)
31 else
32 distance_without_maximum(str1, str2)
33 end
34 end
35
36 private
37 def distance_with_maximum(str1, str2, max_distance) # :nodoc:
38 s = str1.encode(Encoding::UTF_8).unpack("U*")
39 t = str2.encode(Encoding::UTF_8).unpack("U*")
40
41 n = s.length
42 m = t.length
43 big_int = n * m
44
45 # Swap if necessary so that s is always the shorter of the two strings
46 s, t, n, m = t, s, m, n if m < n
47
48 # If the length difference is already greater than the max_distance, then
49 # there is nothing else to check
50 if (n - m).abs >= max_distance
51 return max_distance
52 end
53
54 return 0 if s == t
55 return m if n.zero?
56 return n if m.zero?
57
58 # The values necessary for our threshold are written; the ones after must
59 # be filled with large integers since the tailing member of the threshold
60 # window in the bottom array will run min across them
61 d = (m + 1).times.map { |i|
62 if i < m || i < max_distance + 1
63 i
64 else
65 big_int
66 end
67 }
68 x = nil
69 e = nil
70
71 n.times do |i|
72 # Since we're reusing arrays, we need to be sure to wipe the value left
73 # of the starting index; we don't have to worry about the value above the
74 # ending index as the arrays were initially filled with large integers
75 # and we progress to the right
76 if e.nil?
77 e = i + 1
78 else
79 e = big_int
80 end
81
82 diag_index = t.length - s.length + i
83
84 # If max_distance was specified, we can reduce second loop. So we set
85 # up our threshold window.
86 # See:
87 # Gusfield, Dan (1997). Algorithms on strings, trees, and sequences:
88 # computer science and computational biology.
89 # Cambridge, UK: Cambridge University Press. ISBN 0-521-58519-8.
90 # pp. 263–264.
91 min = i - max_distance - 1
92 min = 0 if min < 0
93 max = i + max_distance
94 max = m - 1 if max > m - 1
95
96 min.upto(max) do |j|
97 # If the diagonal value is already greater than the max_distance
98 # then we can safety return: the diagonal will never go lower again.
99 # See: http://www.levenshtein.net/
100 if j == diag_index && d[j] >= max_distance
101 return max_distance
102 end
103
104 cost = s[i] == t[j] ? 0 : 1
105 insertion = d[j + 1] + 1
106 deletion = e + 1
107 substitution = d[j] + cost
108 x = insertion < deletion ? insertion : deletion
109 x = substitution if substitution < x
110
111 d[j] = e
112 e = x
113 end
114 d[m] = x
115 end
116
117 if x > max_distance
118 return max_distance
119 else
120 return x
121 end
122 end
123
124 def distance_without_maximum(str1, str2) # :nodoc:
125 s = str1.encode(Encoding::UTF_8).unpack("U*")
126 t = str2.encode(Encoding::UTF_8).unpack("U*")
127
128 n = s.length
129 m = t.length
130
131 return m if n.zero?
132 return n if m.zero?
133
134 d = (0..m).to_a
135 x = nil
136
137 n.times do |i|
138 e = i + 1
139 m.times do |j|
140 cost = s[i] == t[j] ? 0 : 1
141 insertion = d[j + 1] + 1
142 deletion = e + 1
143 substitution = d[j] + cost
144 x = insertion < deletion ? insertion : deletion
145 x = substitution if substitution < x
146
147 d[j] = e
148 e = x
149 end
150 d[m] = x
151 end
152
153 return x
154 end
155
156 extend self
157 end
158 end
+0
-97
vendor/text/metaphone.rb less more
0 #
1 # An implementation of the Metaphone phonetic coding system in Ruby.
2 #
3 # Metaphone encodes names into a phonetic form such that similar-sounding names
4 # have the same or similar Metaphone encodings.
5 #
6 # The original system was described by Lawrence Philips in Computer Language
7 # Vol. 7 No. 12, December 1990, pp 39-43.
8 #
9 # As there are multiple implementations of Metaphone, each with their own
10 # quirks, I have based this on my interpretation of the algorithm specification.
11 # Even LP's original BASIC implementation appears to contain bugs (specifically
12 # with the handling of CC and MB), when compared to his explanation of the
13 # algorithm.
14 #
15 # I have also compared this implementation with that found in PHP's standard
16 # library, which appears to mimic the behaviour of LP's original BASIC
17 # implementation. For compatibility, these rules can also be used by passing
18 # :buggy=>true to the methods.
19 #
20 # Author: Paul Battley (pbattley@gmail.com)
21 #
22
23 module Text # :nodoc:
24 module Metaphone
25
26 module Rules # :nodoc:all
27
28 # Metaphone rules. These are simply applied in order.
29 #
30 STANDARD = [
31 # Regexp, replacement
32 [ /([bcdfhjklmnpqrstvwxyz])\1+/,
33 '\1' ], # Remove doubled consonants except g.
34 # [PHP] remove c from regexp.
35 [ /^ae/, 'E' ],
36 [ /^[gkp]n/, 'N' ],
37 [ /^wr/, 'R' ],
38 [ /^x/, 'S' ],
39 [ /^wh/, 'W' ],
40 [ /mb$/, 'M' ], # [PHP] remove $ from regexp.
41 [ /(?!^)sch/, 'SK' ],
42 [ /th/, '0' ],
43 [ /t?ch|sh/, 'X' ],
44 [ /c(?=ia)/, 'X' ],
45 [ /[st](?=i[ao])/, 'X' ],
46 [ /s?c(?=[iey])/, 'S' ],
47 [ /(ck?|q)/, 'K' ],
48 [ /dg(?=[iey])/, 'J' ],
49 [ /d/, 'T' ],
50 [ /g(?=h[^aeiou])/, '' ],
51 [ /gn(ed)?/, 'N' ],
52 [ /([^g]|^)g(?=[iey])/,
53 '\1J' ],
54 [ /g+/, 'K' ],
55 [ /ph/, 'F' ],
56 [ /([aeiou])h(?=\b|[^aeiou])/,
57 '\1' ],
58 [ /[wy](?![aeiou])/, '' ],
59 [ /z/, 'S' ],
60 [ /v/, 'F' ],
61 [ /(?!^)[aeiou]+/, '' ],
62 ]
63
64 # The rules for the 'buggy' alternate implementation used by PHP etc.
65 #
66 BUGGY = STANDARD.dup
67 BUGGY[0] = [ /([bdfhjklmnpqrstvwxyz])\1+/, '\1' ]
68 BUGGY[6] = [ /mb/, 'M' ]
69 end
70
71 # Returns the Metaphone representation of a string. If the string contains
72 # multiple words, each word in turn is converted into its Metaphone
73 # representation. Note that only the letters A-Z are supported, so any
74 # language-specific processing should be done beforehand.
75 #
76 # If the :buggy option is set, alternate 'buggy' rules are used.
77 #
78 def metaphone(str, options={})
79 return str.strip.split(/\s+/).map { |w| metaphone_word(w, options) }.join(' ')
80 end
81
82 private
83
84 def metaphone_word(w, options={})
85 # Normalise case and remove non-ASCII
86 s = w.downcase.gsub(/[^a-z]/, '')
87 # Apply the Metaphone rules
88 rules = options[:buggy] ? Rules::BUGGY : Rules::STANDARD
89 rules.each { |rx, rep| s.gsub!(rx, rep) }
90 return s.upcase
91 end
92
93 extend self
94
95 end
96 end
+0
-171
vendor/text/porter_stemming.rb less more
0 #
1 # This is the Porter Stemming algorithm, ported to Ruby from the
2 # version coded up in Perl. It's easy to follow against the rules
3 # in the original paper in:
4 #
5 # Porter, 1980, An algorithm for suffix stripping, Program, Vol. 14,
6 # no. 3, pp 130-137,
7 #
8 # Taken from http://www.tartarus.org/~martin/PorterStemmer (Public Domain)
9 #
10 module Text # :nodoc:
11 module PorterStemming
12
13 STEP_2_LIST = {
14 'ational' => 'ate', 'tional' => 'tion', 'enci' => 'ence', 'anci' => 'ance',
15 'izer' => 'ize', 'bli' => 'ble',
16 'alli' => 'al', 'entli' => 'ent', 'eli' => 'e', 'ousli' => 'ous',
17 'ization' => 'ize', 'ation' => 'ate',
18 'ator' => 'ate', 'alism' => 'al', 'iveness' => 'ive', 'fulness' => 'ful',
19 'ousness' => 'ous', 'aliti' => 'al',
20 'iviti' => 'ive', 'biliti' => 'ble', 'logi' => 'log'
21 }
22
23 STEP_3_LIST = {
24 'icate' => 'ic', 'ative' => '', 'alize' => 'al', 'iciti' => 'ic',
25 'ical' => 'ic', 'ful' => '', 'ness' => ''
26 }
27
28 SUFFIX_1_REGEXP = /(
29 ational |
30 tional |
31 enci |
32 anci |
33 izer |
34 bli |
35 alli |
36 entli |
37 eli |
38 ousli |
39 ization |
40 ation |
41 ator |
42 alism |
43 iveness |
44 fulness |
45 ousness |
46 aliti |
47 iviti |
48 biliti |
49 logi)$/x
50
51 SUFFIX_2_REGEXP = /(
52 al |
53 ance |
54 ence |
55 er |
56 ic |
57 able |
58 ible |
59 ant |
60 ement |
61 ment |
62 ent |
63 ou |
64 ism |
65 ate |
66 iti |
67 ous |
68 ive |
69 ize)$/x
70
71 C = "[^aeiou]" # consonant
72 V = "[aeiouy]" # vowel
73 CC = "#{C}(?>[^aeiouy]*)" # consonant sequence
74 VV = "#{V}(?>[aeiou]*)" # vowel sequence
75
76 MGR0 = /^(#{CC})?#{VV}#{CC}/o # [cc]vvcc... is m>0
77 MEQ1 = /^(#{CC})?#{VV}#{CC}(#{VV})?$/o # [cc]vvcc[vv] is m=1
78 MGR1 = /^(#{CC})?#{VV}#{CC}#{VV}#{CC}/o # [cc]vvccvvcc... is m>1
79 VOWEL_IN_STEM = /^(#{CC})?#{V}/o # vowel in stem
80
81 def self.stem(word)
82
83 # make a copy of the given object and convert it to a string.
84 word = word.dup.to_str
85
86 return word if word.length < 3
87
88 # now map initial y to Y so that the patterns never treat it as vowel
89 word[0] = 'Y' if word[0] == ?y
90
91 # Step 1a
92 if word =~ /(ss|i)es$/
93 word = $` + $1
94 elsif word =~ /([^s])s$/
95 word = $` + $1
96 end
97
98 # Step 1b
99 if word =~ /eed$/
100 word.chop! if $` =~ MGR0
101 elsif word =~ /(ed|ing)$/
102 stem = $`
103 if stem =~ VOWEL_IN_STEM
104 word = stem
105 case word
106 when /(at|bl|iz)$/ then word << "e"
107 when /([^aeiouylsz])\1$/ then word.chop!
108 when /^#{CC}#{V}[^aeiouwxy]$/o then word << "e"
109 end
110 end
111 end
112
113 if word =~ /y$/
114 stem = $`
115 word = stem + "i" if stem =~ VOWEL_IN_STEM
116 end
117
118 # Step 2
119 if word =~ SUFFIX_1_REGEXP
120 stem = $`
121 suffix = $1
122 # print "stem= " + stem + "\n" + "suffix=" + suffix + "\n"
123 if stem =~ MGR0
124 word = stem + STEP_2_LIST[suffix]
125 end
126 end
127
128 # Step 3
129 if word =~ /(icate|ative|alize|iciti|ical|ful|ness)$/
130 stem = $`
131 suffix = $1
132 if stem =~ MGR0
133 word = stem + STEP_3_LIST[suffix]
134 end
135 end
136
137 # Step 4
138 if word =~ SUFFIX_2_REGEXP
139 stem = $`
140 if stem =~ MGR1
141 word = stem
142 end
143 elsif word =~ /(s|t)(ion)$/
144 stem = $` + $1
145 if stem =~ MGR1
146 word = stem
147 end
148 end
149
150 # Step 5
151 if word =~ /e$/
152 stem = $`
153 if (stem =~ MGR1) ||
154 (stem =~ MEQ1 && stem !~ /^#{CC}#{V}[^aeiouwxy]$/o)
155 word = stem
156 end
157 end
158
159 if word =~ /ll$/ && word =~ MGR1
160 word.chop!
161 end
162
163 # and turn initial Y back to y
164 word[0] = 'y' if word[0] == ?Y
165
166 word
167 end
168
169 end
170 end
+0
-59
vendor/text/soundex.rb less more
0 #
1 # Ruby implementation of the Soundex algorithm,
2 # as described by Knuth in volume 3 of The Art of Computer Programming.
3 #
4 # Author: Michael Neumann (neumann@s-direktnet.de)
5 #
6
7 module Text # :nodoc:
8 module Soundex
9
10 def soundex(str_or_arr)
11 case str_or_arr
12 when String
13 soundex_str(str_or_arr)
14 when Array
15 str_or_arr.collect{|ele| soundex_str(ele)}
16 else
17 nil
18 end
19 end
20 module_function :soundex
21
22 private
23
24 #
25 # returns nil if the value couldn't be calculated (empty-string, wrong-character)
26 # do not change the parameter "str"
27 #
28 def soundex_str(str)
29 str = str.upcase.gsub(/[^A-Z]/, "")
30 return nil if str.empty?
31
32 last_code = get_code(str[0,1])
33 soundex_code = str[0,1]
34
35 for index in 1...(str.size) do
36 return soundex_code if soundex_code.size == 4
37
38 code = get_code(str[index,1])
39
40 if code == "0" then
41 last_code = nil
42 elsif code != last_code then
43 soundex_code += code
44 last_code = code
45 end
46 end # for
47
48 return soundex_code.ljust(4, "0")
49 end
50 module_function :soundex_str
51
52 def get_code(char)
53 char.tr! "AEIOUYWHBPFVCSKGJQXZDTLMNR", "00000000111122222222334556"
54 end
55 module_function :get_code
56
57 end # module Soundex
58 end # module Text
+0
-9
vendor/text/version.rb less more
0 module Text
1 module VERSION #:nodoc:
2 MAJOR = 1
3 MINOR = 3
4 TINY = 1
5
6 STRING = [MAJOR, MINOR, TINY].join('.')
7 end
8 end
+0
-61
vendor/text/white_similarity.rb less more
0 # encoding: utf-8
1 # Original author: Wilker Lúcio <wilkerlucio@gmail.com>
2
3 require "set"
4
5 module Text
6
7 # Ruby implementation of the string similarity described by Simon White
8 # at: http://www.catalysoft.com/articles/StrikeAMatch.html
9 #
10 # 2 * |pairs(s1) INTERSECT pairs(s2)|
11 # similarity(s1, s2) = -----------------------------------
12 # |pairs(s1)| + |pairs(s2)|
13 #
14 # e.g.
15 # 2 * |{FR, NC}|
16 # similarity(FRANCE, FRENCH) = ---------------------------------------
17 # |{FR,RA,AN,NC,CE}| + |{FR,RE,EN,NC,CH}|
18 #
19 # = (2 * 2) / (5 + 5)
20 #
21 # = 0.4
22 #
23 # WhiteSimilarity.new.similarity("FRANCE", "FRENCH")
24 #
25 class WhiteSimilarity
26
27 def self.similarity(str1, str2)
28 new.similarity(str1, str2)
29 end
30
31 def initialize
32 @word_letter_pairs = {}
33 end
34
35 def similarity(str1, str2)
36 pairs1 = word_letter_pairs(str1)
37 pairs2 = word_letter_pairs(str2).dup
38
39 union = pairs1.length + pairs2.length
40
41 intersection = 0
42 pairs1.each do |pair1|
43 if index = pairs2.index(pair1)
44 intersection += 1
45 pairs2.delete_at(index)
46 end
47 end
48
49 (2.0 * intersection) / union
50 end
51
52 private
53 def word_letter_pairs(str)
54 @word_letter_pairs[str] ||=
55 str.upcase.split(/\s+/).map{ |word|
56 (0 ... (word.length - 1)).map { |i| word[i, 2] }
57 }.flatten.freeze
58 end
59 end
60 end
+0
-7
vendor/text.rb less more
0 require 'text/double_metaphone'
1 require 'text/levenshtein'
2 require 'text/metaphone'
3 require 'text/porter_stemming'
4 require 'text/soundex'
5 require 'text/version'
6 require 'text/white_similarity'
vendor/typed-array/.DS_Store less more
Binary diff not shown
+0
-115
vendor/typed-array/functions.rb less more
0 # Provides the validation functions that get included into a TypedArray
1
2 # Namespace TypedArray
3 module TypedArray
4
5 # The functions that get included into TypedArray
6 module Functions
7 # Validates outcome. See Array#initialize
8 def initialize *args, &block
9 ary = Array.new *args, &block
10 self.replace ary
11 end
12
13 # Validates outcome. See Array#replace
14 def replace other_ary
15 _ensure_all_items_in_array_are_allowed other_ary
16 super
17 end
18
19 # Validates outcome. See Array#&
20 def & ary
21 self.class.new super
22 end
23
24 # Validates outcome. See Array#*
25 def * int
26 self.class.new super
27 end
28
29 # Validates outcome. See Array#+
30 def + ary
31 self.class.new super
32 end
33
34 # Validates outcome. See Array#<<
35 def << item
36 _ensure_item_is_allowed item
37 super
38 end
39
40 # Validates outcome. See Array#[]
41 def [] idx
42 self.class.new super
43 end
44
45 # Validates outcome. See Array#slice
46 def slice *args
47 self.class.new super
48 end
49
50 # Validates outcome. See Array#[]=
51 def []= idx, item
52 _ensure_item_is_allowed item
53 super
54 end
55
56 # Validates outcome. See Array#concat
57 def concat other_ary
58 _ensure_all_items_in_array_are_allowed other_ary
59 super
60 end
61
62 # Validates outcome. See Array#eql?
63 def eql? other_ary
64 _ensure_all_items_in_array_are_allowed other_ary
65 super
66 end
67
68 # Validates outcome. See Array#fill
69 def fill *args, &block
70 ary = self.to_a
71 ary.fill *args, &block
72 self.replace ary
73 end
74
75 # Validates outcome. See Array#push
76 def push *items
77 _ensure_all_items_in_array_are_allowed items
78 super
79 end
80
81 # Validates outcome. See Array#unshift
82 def unshift *items
83 _ensure_all_items_in_array_are_allowed items
84 super
85 end
86
87 # Validates outcome. See Array#map!
88 def map! &block
89 self.replace( self.map &block )
90 end
91
92 protected
93
94 # Ensure that all items in the passed Array are allowed
95 def _ensure_all_items_in_array_are_allowed ary
96 # If we're getting an instance of self, accept
97 return true if ary.is_a? self.class
98 _ensure_item_is_allowed( ary, [Array] )
99 ary.each do |item|
100 _ensure_item_is_allowed(item)
101 end
102 end
103
104 # Ensure that the specific item passed is allowed
105 def _ensure_item_is_allowed item, expected=nil
106 return true if item.nil? #allow nil entries
107 expected = self.class.restricted_types if expected.nil?
108 expected.each do |allowed|
109 return true if item.class <= allowed
110 end
111 raise TypedArray::UnexpectedTypeException.new expected, item.class
112 end
113 end
114 end
+0
-76
vendor/typed-array.rb less more
0 # :include: ../README.rdoc
1
2 require "typed-array/functions"
3
4 # Provides TypedArray functionality to a subclass of Array
5 # when extended in the class's definiton
6 module TypedArray
7
8 # Hook the extension process in order to include the necessary functions
9 # and do some basic sanity checks.
10 def self.extended( mod )
11 unless mod <= Array
12 raise UnexpectedTypeException.new( [Array], mod.class )
13 end
14 mod.module_exec(self::Functions) do |functions_module|
15 include functions_module
16 end
17 end
18
19 # when a class inherits from this one, make sure that it also inherits
20 # the types that are being enforced
21 def inherited( subclass )
22 self._subclasses << subclass
23 subclass.restricted_types *restricted_types
24 end
25
26 # A getter/setter for types to add. If no arguments are passed, it simply
27 # returns the current array of accepted types.
28 def restricted_types(*types)
29 @_restricted_types ||= []
30 types.each do |type|
31 raise UnexpectedTypeException.new([Class],type.class) unless type.is_a? Class
32 @_restricted_types << type unless @_restricted_types.include? type
33 _subclasses.each do |subclass|
34 subclass.restricted_types type
35 end
36 end
37 @_restricted_types
38 end; alias :restricted_type :restricted_types
39
40 # The exception that is raised when an Unexpected Type is reached during validation
41 class UnexpectedTypeException < Exception
42 # Provide access to the types of objects expected and the class of the object received
43 attr_reader :expected, :received
44
45 def initialize expected_one_of, received
46 @expected = expected_one_of
47 @received = received
48 end
49
50 def to_s
51 %{Expected one of #{@expected.inspect} but received a(n) #{@received}}
52 end
53 end
54
55 protected
56
57 # a store of subclasses
58 def _subclasses
59 @_subclasses ||= []
60 end
61
62 end
63
64 # Provide a factory method. Takes any number of types to accept as arguments
65 # and returns a class that behaves as a type-enforced array.
66 def TypedArray *types_allowed
67 klass = Class.new( Array )
68 klass.class_exec(types_allowed) do |types_allowed|
69 extend TypedArray
70 restricted_types *types_allowed
71 restricted_types
72 end
73 klass.restricted_types
74 klass
75 end