Codebase list mikutter / 912ac2d
Imported Upstream version 3.2.0~alpha1+dfsg HIGUCHI Daisuke (VDR dai) 9 years ago
194 changed file(s) with 0 addition(s) and 27580 deletion(s). Raw diff Collapse all Expand all
core/skin/data/icon.png less more
Binary diff not shown
+0
-43
vendor/addressable/idna/native.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2013 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)
24 end
25
26 def self.punycode_decode(value)
27 IDN::Punycode.decode(value)
28 end
29
30 def self.unicode_normalize_kc(value)
31 IDN::Stringprep.nfkc_normalize(value)
32 end
33
34 def self.to_ascii(value)
35 IDN::Idna.toASCII(value)
36 end
37
38 def self.to_unicode(value)
39 IDN::Idna.toUnicode(value)
40 end
41 end
42 end
+0
-669
vendor/addressable/idna/pure.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2013 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.dup
67 if input.respond_to?(:force_encoding)
68 input.force_encoding(Encoding::ASCII_8BIT)
69 end
70 if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE
71 parts = unicode_downcase(input).split('.')
72 parts.map! do |part|
73 if part.respond_to?(:force_encoding)
74 part.force_encoding(Encoding::ASCII_8BIT)
75 end
76 if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
77 ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
78 else
79 part
80 end
81 end
82 parts.join('.')
83 else
84 input
85 end
86 end
87
88 # Converts from an ASCII domain name to a Unicode internationalized
89 # domain name as described in RFC 3490.
90 def self.to_unicode(input)
91 parts = input.split('.')
92 parts.map! do |part|
93 if part =~ /^#{ACE_PREFIX}/
94 punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
95 else
96 part
97 end
98 end
99 output = parts.join('.')
100 if output.respond_to?(:force_encoding)
101 output.force_encoding(Encoding::UTF_8)
102 end
103 output
104 end
105
106 # Unicode normalization form KC.
107 def self.unicode_normalize_kc(input)
108 input = input.to_s unless input.is_a?(String)
109 unpacked = input.unpack("U*")
110 unpacked =
111 unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
112 return unpacked.pack("U*")
113 end
114
115 ##
116 # Unicode aware downcase method.
117 #
118 # @api private
119 # @param [String] input
120 # The input string.
121 # @return [String] The downcased result.
122 def self.unicode_downcase(input)
123 unpacked = input.unpack("U*")
124 unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
125 return unpacked.pack("U*")
126 end
127 (class <<self; private :unicode_downcase; end)
128
129 def self.unicode_compose(unpacked)
130 unpacked_result = []
131 length = unpacked.length
132
133 return unpacked if length == 0
134
135 starter = unpacked[0]
136 starter_cc = lookup_unicode_combining_class(starter)
137 starter_cc = 256 if starter_cc != 0
138 for i in 1...length
139 ch = unpacked[i]
140 cc = lookup_unicode_combining_class(ch)
141
142 if (starter_cc == 0 &&
143 (composite = unicode_compose_pair(starter, ch)) != nil)
144 starter = composite
145 startercc = lookup_unicode_combining_class(composite)
146 else
147 unpacked_result << starter
148 starter = ch
149 startercc = cc
150 end
151 end
152 unpacked_result << starter
153 return unpacked_result
154 end
155 (class <<self; private :unicode_compose; end)
156
157 def self.unicode_compose_pair(ch_one, ch_two)
158 if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
159 ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
160 # Hangul L + V
161 return HANGUL_SBASE + (
162 (ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
163 ) * HANGUL_TCOUNT
164 elsif ch_one >= HANGUL_SBASE &&
165 ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
166 (ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
167 ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
168 # Hangul LV + T
169 return ch_one + (ch_two - HANGUL_TBASE)
170 end
171
172 p = []
173 ucs4_to_utf8 = lambda do |ch|
174 # For some reason, rcov likes to drop BUS errors here.
175 if ch < 128
176 p << ch
177 elsif ch < 2048
178 p << (ch >> 6 | 192)
179 p << (ch & 63 | 128)
180 elsif ch < 0x10000
181 p << (ch >> 12 | 224)
182 p << (ch >> 6 & 63 | 128)
183 p << (ch & 63 | 128)
184 elsif ch < 0x200000
185 p << (ch >> 18 | 240)
186 p << (ch >> 12 & 63 | 128)
187 p << (ch >> 6 & 63 | 128)
188 p << (ch & 63 | 128)
189 elsif ch < 0x4000000
190 p << (ch >> 24 | 248)
191 p << (ch >> 18 & 63 | 128)
192 p << (ch >> 12 & 63 | 128)
193 p << (ch >> 6 & 63 | 128)
194 p << (ch & 63 | 128)
195 elsif ch < 0x80000000
196 p << (ch >> 30 | 252)
197 p << (ch >> 24 & 63 | 128)
198 p << (ch >> 18 & 63 | 128)
199 p << (ch >> 12 & 63 | 128)
200 p << (ch >> 6 & 63 | 128)
201 p << (ch & 63 | 128)
202 end
203 end
204
205 ucs4_to_utf8.call(ch_one)
206 ucs4_to_utf8.call(ch_two)
207
208 return lookup_unicode_composition(p)
209 end
210 (class <<self; private :unicode_compose_pair; end)
211
212 def self.unicode_sort_canonical(unpacked)
213 unpacked = unpacked.dup
214 i = 1
215 length = unpacked.length
216
217 return unpacked if length < 2
218
219 while i < length
220 last = unpacked[i-1]
221 ch = unpacked[i]
222 last_cc = lookup_unicode_combining_class(last)
223 cc = lookup_unicode_combining_class(ch)
224 if cc != 0 && last_cc != 0 && last_cc > cc
225 unpacked[i] = last
226 unpacked[i-1] = ch
227 i -= 1 if i > 1
228 else
229 i += 1
230 end
231 end
232 return unpacked
233 end
234 (class <<self; private :unicode_sort_canonical; end)
235
236 def self.unicode_decompose(unpacked)
237 unpacked_result = []
238 for cp in unpacked
239 if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
240 l, v, t = unicode_decompose_hangul(cp)
241 unpacked_result << l
242 unpacked_result << v if v
243 unpacked_result << t if t
244 else
245 dc = lookup_unicode_compatibility(cp)
246 unless dc
247 unpacked_result << cp
248 else
249 unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
250 end
251 end
252 end
253 return unpacked_result
254 end
255 (class <<self; private :unicode_decompose; end)
256
257 def self.unicode_decompose_hangul(codepoint)
258 sindex = codepoint - HANGUL_SBASE;
259 if sindex < 0 || sindex >= HANGUL_SCOUNT
260 l = codepoint
261 v = t = nil
262 return l, v, t
263 end
264 l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
265 v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
266 t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
267 if t == HANGUL_TBASE
268 t = nil
269 end
270 return l, v, t
271 end
272 (class <<self; private :unicode_decompose_hangul; end)
273
274 def self.lookup_unicode_combining_class(codepoint)
275 codepoint_data = UNICODE_DATA[codepoint]
276 (codepoint_data ?
277 (codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
278 0)
279 end
280 (class <<self; private :lookup_unicode_combining_class; end)
281
282 def self.lookup_unicode_compatibility(codepoint)
283 codepoint_data = UNICODE_DATA[codepoint]
284 (codepoint_data ?
285 codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
286 end
287 (class <<self; private :lookup_unicode_compatibility; end)
288
289 def self.lookup_unicode_lowercase(codepoint)
290 codepoint_data = UNICODE_DATA[codepoint]
291 (codepoint_data ?
292 (codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) :
293 codepoint)
294 end
295 (class <<self; private :lookup_unicode_lowercase; end)
296
297 def self.lookup_unicode_composition(unpacked)
298 return COMPOSITION_TABLE[unpacked]
299 end
300 (class <<self; private :lookup_unicode_composition; end)
301
302 HANGUL_SBASE = 0xac00
303 HANGUL_LBASE = 0x1100
304 HANGUL_LCOUNT = 19
305 HANGUL_VBASE = 0x1161
306 HANGUL_VCOUNT = 21
307 HANGUL_TBASE = 0x11a7
308 HANGUL_TCOUNT = 28
309 HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
310 HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
311
312 UNICODE_DATA_COMBINING_CLASS = 0
313 UNICODE_DATA_EXCLUSION = 1
314 UNICODE_DATA_CANONICAL = 2
315 UNICODE_DATA_COMPATIBILITY = 3
316 UNICODE_DATA_UPPERCASE = 4
317 UNICODE_DATA_LOWERCASE = 5
318 UNICODE_DATA_TITLECASE = 6
319
320 begin
321 if defined?(FakeFS)
322 fakefs_state = FakeFS.activated?
323 FakeFS.deactivate!
324 end
325 # This is a sparse Unicode table. Codepoints without entries are
326 # assumed to have the value: [0, 0, nil, nil, nil, nil, nil]
327 UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file|
328 Marshal.load(file.read)
329 end
330 ensure
331 if defined?(FakeFS)
332 FakeFS.activate! if fakefs_state
333 end
334 end
335
336 COMPOSITION_TABLE = {}
337 for codepoint, data in UNICODE_DATA
338 canonical = data[UNICODE_DATA_CANONICAL]
339 exclusion = data[UNICODE_DATA_EXCLUSION]
340
341 if canonical && exclusion == 0
342 COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint
343 end
344 end
345
346 UNICODE_MAX_LENGTH = 256
347 ACE_MAX_LENGTH = 256
348
349 PUNYCODE_BASE = 36
350 PUNYCODE_TMIN = 1
351 PUNYCODE_TMAX = 26
352 PUNYCODE_SKEW = 38
353 PUNYCODE_DAMP = 700
354 PUNYCODE_INITIAL_BIAS = 72
355 PUNYCODE_INITIAL_N = 0x80
356 PUNYCODE_DELIMITER = 0x2D
357
358 PUNYCODE_MAXINT = 1 << 64
359
360 PUNYCODE_PRINT_ASCII =
361 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
362 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
363 " !\"\#$%&'()*+,-./" +
364 "0123456789:;<=>?" +
365 "@ABCDEFGHIJKLMNO" +
366 "PQRSTUVWXYZ[\\]^_" +
367 "`abcdefghijklmno" +
368 "pqrstuvwxyz{|}~\n"
369
370 # Input is invalid.
371 class PunycodeBadInput < StandardError; end
372 # Output would exceed the space provided.
373 class PunycodeBigOutput < StandardError; end
374 # Input needs wider integers to process.
375 class PunycodeOverflow < StandardError; end
376
377 def self.punycode_encode(unicode)
378 input = unicode.unpack("U*")
379 output = [0] * (ACE_MAX_LENGTH + 1)
380 input_length = input.size
381 output_length = [ACE_MAX_LENGTH]
382
383 # Initialize the state
384 n = PUNYCODE_INITIAL_N
385 delta = out = 0
386 max_out = output_length[0]
387 bias = PUNYCODE_INITIAL_BIAS
388
389 # Handle the basic code points:
390 input_length.times do |j|
391 if punycode_basic?(input[j])
392 if max_out - out < 2
393 raise PunycodeBigOutput,
394 "Output would exceed the space provided."
395 end
396 output[out] = input[j]
397 out += 1
398 end
399 end
400
401 h = b = out
402
403 # h is the number of code points that have been handled, b is the
404 # number of basic code points, and out is the number of characters
405 # that have been output.
406
407 if b > 0
408 output[out] = PUNYCODE_DELIMITER
409 out += 1
410 end
411
412 # Main encoding loop:
413
414 while h < input_length
415 # All non-basic code points < n have been
416 # handled already. Find the next larger one:
417
418 m = PUNYCODE_MAXINT
419 input_length.times do |j|
420 m = input[j] if (n...m) === input[j]
421 end
422
423 # Increase delta enough to advance the decoder's
424 # <n,i> state to <m,0>, but guard against overflow:
425
426 if m - n > (PUNYCODE_MAXINT - delta) / (h + 1)
427 raise PunycodeOverflow, "Input needs wider integers to process."
428 end
429 delta += (m - n) * (h + 1)
430 n = m
431
432 input_length.times do |j|
433 # Punycode does not need to check whether input[j] is basic:
434 if input[j] < n
435 delta += 1
436 if delta == 0
437 raise PunycodeOverflow,
438 "Input needs wider integers to process."
439 end
440 end
441
442 if input[j] == n
443 # Represent delta as a generalized variable-length integer:
444
445 q = delta; k = PUNYCODE_BASE
446 while true
447 if out >= max_out
448 raise PunycodeBigOutput,
449 "Output would exceed the space provided."
450 end
451 t = (
452 if k <= bias
453 PUNYCODE_TMIN
454 elsif k >= bias + PUNYCODE_TMAX
455 PUNYCODE_TMAX
456 else
457 k - bias
458 end
459 )
460 break if q < t
461 output[out] =
462 punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
463 out += 1
464 q = (q - t) / (PUNYCODE_BASE - t)
465 k += PUNYCODE_BASE
466 end
467
468 output[out] = punycode_encode_digit(q)
469 out += 1
470 bias = punycode_adapt(delta, h + 1, h == b)
471 delta = 0
472 h += 1
473 end
474 end
475
476 delta += 1
477 n += 1
478 end
479
480 output_length[0] = out
481
482 outlen = out
483 outlen.times do |j|
484 c = output[j]
485 unless c >= 0 && c <= 127
486 raise Exception, "Invalid output char."
487 end
488 unless PUNYCODE_PRINT_ASCII[c]
489 raise PunycodeBadInput, "Input is invalid."
490 end
491 end
492
493 output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
494 end
495 (class <<self; private :punycode_encode; end)
496
497 def self.punycode_decode(punycode)
498 input = []
499 output = []
500
501 if ACE_MAX_LENGTH * 2 < punycode.size
502 raise PunycodeBigOutput, "Output would exceed the space provided."
503 end
504 punycode.each_byte do |c|
505 unless c >= 0 && c <= 127
506 raise PunycodeBadInput, "Input is invalid."
507 end
508 input.push(c)
509 end
510
511 input_length = input.length
512 output_length = [UNICODE_MAX_LENGTH]
513
514 # Initialize the state
515 n = PUNYCODE_INITIAL_N
516
517 out = i = 0
518 max_out = output_length[0]
519 bias = PUNYCODE_INITIAL_BIAS
520
521 # Handle the basic code points: Let b be the number of input code
522 # points before the last delimiter, or 0 if there is none, then
523 # copy the first b code points to the output.
524
525 b = 0
526 input_length.times do |j|
527 b = j if punycode_delimiter?(input[j])
528 end
529 if b > max_out
530 raise PunycodeBigOutput, "Output would exceed the space provided."
531 end
532
533 b.times do |j|
534 unless punycode_basic?(input[j])
535 raise PunycodeBadInput, "Input is invalid."
536 end
537 output[out] = input[j]
538 out+=1
539 end
540
541 # Main decoding loop: Start just after the last delimiter if any
542 # basic code points were copied; start at the beginning otherwise.
543
544 in_ = b > 0 ? b + 1 : 0
545 while in_ < input_length
546
547 # in_ is the index of the next character to be consumed, and
548 # out is the number of code points in the output array.
549
550 # Decode a generalized variable-length integer into delta,
551 # which gets added to i. The overflow checking is easier
552 # if we increase i as we go, then subtract off its starting
553 # value at the end to obtain delta.
554
555 oldi = i; w = 1; k = PUNYCODE_BASE
556 while true
557 if in_ >= input_length
558 raise PunycodeBadInput, "Input is invalid."
559 end
560 digit = punycode_decode_digit(input[in_])
561 in_+=1
562 if digit >= PUNYCODE_BASE
563 raise PunycodeBadInput, "Input is invalid."
564 end
565 if digit > (PUNYCODE_MAXINT - i) / w
566 raise PunycodeOverflow, "Input needs wider integers to process."
567 end
568 i += digit * w
569 t = (
570 if k <= bias
571 PUNYCODE_TMIN
572 elsif k >= bias + PUNYCODE_TMAX
573 PUNYCODE_TMAX
574 else
575 k - bias
576 end
577 )
578 break if digit < t
579 if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t)
580 raise PunycodeOverflow, "Input needs wider integers to process."
581 end
582 w *= PUNYCODE_BASE - t
583 k += PUNYCODE_BASE
584 end
585
586 bias = punycode_adapt(i - oldi, out + 1, oldi == 0)
587
588 # I was supposed to wrap around from out + 1 to 0,
589 # incrementing n each time, so we'll fix that now:
590
591 if i / (out + 1) > PUNYCODE_MAXINT - n
592 raise PunycodeOverflow, "Input needs wider integers to process."
593 end
594 n += i / (out + 1)
595 i %= out + 1
596
597 # Insert n at position i of the output:
598
599 # not needed for Punycode:
600 # raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base
601 if out >= max_out
602 raise PunycodeBigOutput, "Output would exceed the space provided."
603 end
604
605 #memmove(output + i + 1, output + i, (out - i) * sizeof *output)
606 output[i + 1, out - i] = output[i, out - i]
607 output[i] = n
608 i += 1
609
610 out += 1
611 end
612
613 output_length[0] = out
614
615 output.pack("U*")
616 end
617 (class <<self; private :punycode_decode; end)
618
619 def self.punycode_basic?(codepoint)
620 codepoint < 0x80
621 end
622 (class <<self; private :punycode_basic?; end)
623
624 def self.punycode_delimiter?(codepoint)
625 codepoint == PUNYCODE_DELIMITER
626 end
627 (class <<self; private :punycode_delimiter?; end)
628
629 def self.punycode_encode_digit(d)
630 d + 22 + 75 * ((d < 26) ? 1 : 0)
631 end
632 (class <<self; private :punycode_encode_digit; end)
633
634 # Returns the numeric value of a basic codepoint
635 # (for use in representing integers) in the range 0 to
636 # base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
637 def self.punycode_decode_digit(codepoint)
638 if codepoint - 48 < 10
639 codepoint - 22
640 elsif codepoint - 65 < 26
641 codepoint - 65
642 elsif codepoint - 97 < 26
643 codepoint - 97
644 else
645 PUNYCODE_BASE
646 end
647 end
648 (class <<self; private :punycode_decode_digit; end)
649
650 # Bias adaptation method
651 def self.punycode_adapt(delta, numpoints, firsttime)
652 delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1
653 # delta >> 1 is a faster way of doing delta / 2
654 delta += delta / numpoints
655 difference = PUNYCODE_BASE - PUNYCODE_TMIN
656
657 k = 0
658 while delta > (difference * PUNYCODE_TMAX) / 2
659 delta /= difference
660 k += PUNYCODE_BASE
661 end
662
663 k + (difference + 1) * delta / (delta + PUNYCODE_SKEW)
664 end
665 (class <<self; private :punycode_adapt; end)
666 end
667 # :startdoc:
668 end
+0
-25
vendor/addressable/idna.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2013 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-2013 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
-2351
vendor/addressable/uri.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2013 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 ##
22 # Addressable is a library for processing links and URIs.
23 module Addressable
24 ##
25 # This is an implementation of a URI parser based on
26 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
27 # <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
28 class URI
29 ##
30 # Raised if something other than a uri is supplied.
31 class InvalidURIError < StandardError
32 end
33
34 ##
35 # Container for the character classes specified in
36 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
37 module CharacterClasses
38 ALPHA = "a-zA-Z"
39 DIGIT = "0-9"
40 GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
41 SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
42 RESERVED = GEN_DELIMS + SUB_DELIMS
43 UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
44 PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
45 SCHEME = ALPHA + DIGIT + "\\-\\+\\."
46 AUTHORITY = PCHAR
47 PATH = PCHAR + "\\/"
48 QUERY = PCHAR + "\\/\\?"
49 FRAGMENT = PCHAR + "\\/\\?"
50 end
51
52 SLASH = '/'
53 EMPTY_STR = ''
54
55 URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
56
57 PORT_MAPPING = {
58 "http" => 80,
59 "https" => 443,
60 "ftp" => 21,
61 "tftp" => 69,
62 "sftp" => 22,
63 "ssh" => 22,
64 "svn+ssh" => 22,
65 "telnet" => 23,
66 "nntp" => 119,
67 "gopher" => 70,
68 "wais" => 210,
69 "ldap" => 389,
70 "prospero" => 1525
71 }
72
73 ##
74 # Returns a URI object based on the parsed string.
75 #
76 # @param [String, Addressable::URI, #to_str] uri
77 # The URI string to parse.
78 # No parsing is performed if the object is already an
79 # <code>Addressable::URI</code>.
80 #
81 # @return [Addressable::URI] The parsed URI.
82 def self.parse(uri)
83 # If we were given nil, return nil.
84 return nil unless uri
85 # If a URI object is passed, just return itself.
86 return uri.dup if uri.kind_of?(self)
87
88 # If a URI object of the Ruby standard library variety is passed,
89 # convert it to a string, then parse the string.
90 # We do the check this way because we don't want to accidentally
91 # cause a missing constant exception to be thrown.
92 if uri.class.name =~ /^URI\b/
93 uri = uri.to_s
94 end
95
96 # Otherwise, convert to a String
97 begin
98 uri = uri.to_str
99 rescue TypeError, NoMethodError
100 raise TypeError, "Can't convert #{uri.class} into String."
101 end if not uri.is_a? String
102
103 # This Regexp supplied as an example in RFC 3986, and it works great.
104 scan = uri.scan(URIREGEX)
105 fragments = scan[0]
106 scheme = fragments[1]
107 authority = fragments[3]
108 path = fragments[4]
109 query = fragments[6]
110 fragment = fragments[8]
111 user = nil
112 password = nil
113 host = nil
114 port = nil
115 if authority != nil
116 # The Regexp above doesn't split apart the authority.
117 userinfo = authority[/^([^\[\]]*)@/, 1]
118 if userinfo != nil
119 user = userinfo.strip[/^([^:]*):?/, 1]
120 password = userinfo.strip[/:(.*)$/, 1]
121 end
122 host = authority.gsub(
123 /^([^\[\]]*)@/, EMPTY_STR
124 ).gsub(
125 /:([^:@\[\]]*?)$/, EMPTY_STR
126 )
127 port = authority[/:([^:@\[\]]*?)$/, 1]
128 end
129 if port == EMPTY_STR
130 port = nil
131 end
132
133 return new(
134 :scheme => scheme,
135 :user => user,
136 :password => password,
137 :host => host,
138 :port => port,
139 :path => path,
140 :query => query,
141 :fragment => fragment
142 )
143 end
144
145 ##
146 # Converts an input to a URI. The input does not have to be a valid
147 # URI — the method will use heuristics to guess what URI was intended.
148 # This is not standards-compliant, merely user-friendly.
149 #
150 # @param [String, Addressable::URI, #to_str] uri
151 # The URI string to parse.
152 # No parsing is performed if the object is already an
153 # <code>Addressable::URI</code>.
154 # @param [Hash] hints
155 # A <code>Hash</code> of hints to the heuristic parser.
156 # Defaults to <code>{:scheme => "http"}</code>.
157 #
158 # @return [Addressable::URI] The parsed URI.
159 def self.heuristic_parse(uri, hints={})
160 # If we were given nil, return nil.
161 return nil unless uri
162 # If a URI object is passed, just return itself.
163 return uri.dup if uri.kind_of?(self)
164
165 # If a URI object of the Ruby standard library variety is passed,
166 # convert it to a string, then parse the string.
167 # We do the check this way because we don't want to accidentally
168 # cause a missing constant exception to be thrown.
169 if uri.class.name =~ /^URI\b/
170 uri = uri.to_s
171 end
172
173 if !uri.respond_to?(:to_str)
174 raise TypeError, "Can't convert #{uri.class} into String."
175 end
176 # Otherwise, convert to a String
177 uri = uri.to_str.dup
178 hints = {
179 :scheme => "http"
180 }.merge(hints)
181 case uri
182 when /^http:\/+/
183 uri.gsub!(/^http:\/+/, "http://")
184 when /^https:\/+/
185 uri.gsub!(/^https:\/+/, "https://")
186 when /^feed:\/+http:\/+/
187 uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
188 when /^feed:\/+/
189 uri.gsub!(/^feed:\/+/, "feed://")
190 when /^file:\/+/
191 uri.gsub!(/^file:\/+/, "file:///")
192 when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
193 uri.gsub!(/^/, hints[:scheme] + "://")
194 end
195 parsed = self.parse(uri)
196 if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
197 parsed = self.parse(hints[:scheme] + "://" + uri)
198 end
199 if parsed.path.include?(".")
200 new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
201 if new_host
202 parsed.defer_validation do
203 new_path = parsed.path.gsub(
204 Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
205 parsed.host = new_host
206 parsed.path = new_path
207 parsed.scheme = hints[:scheme] unless parsed.scheme
208 end
209 end
210 end
211 return parsed
212 end
213
214 ##
215 # Converts a path to a file scheme URI. If the path supplied is
216 # relative, it will be returned as a relative URI. If the path supplied
217 # is actually a non-file URI, it will parse the URI as if it had been
218 # parsed with <code>Addressable::URI.parse</code>. Handles all of the
219 # various Microsoft-specific formats for specifying paths.
220 #
221 # @param [String, Addressable::URI, #to_str] path
222 # Typically a <code>String</code> path to a file or directory, but
223 # will return a sensible return value if an absolute URI is supplied
224 # instead.
225 #
226 # @return [Addressable::URI]
227 # The parsed file scheme URI or the original URI if some other URI
228 # scheme was provided.
229 #
230 # @example
231 # base = Addressable::URI.convert_path("/absolute/path/")
232 # uri = Addressable::URI.convert_path("relative/path")
233 # (base + uri).to_s
234 # #=> "file:///absolute/path/relative/path"
235 #
236 # Addressable::URI.convert_path(
237 # "c:\\windows\\My Documents 100%20\\foo.txt"
238 # ).to_s
239 # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
240 #
241 # Addressable::URI.convert_path("http://example.com/").to_s
242 # #=> "http://example.com/"
243 def self.convert_path(path)
244 # If we were given nil, return nil.
245 return nil unless path
246 # If a URI object is passed, just return itself.
247 return path if path.kind_of?(self)
248 if !path.respond_to?(:to_str)
249 raise TypeError, "Can't convert #{path.class} into String."
250 end
251 # Otherwise, convert to a String
252 path = path.to_str.strip
253
254 path.gsub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
255 path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
256 uri = self.parse(path)
257
258 if uri.scheme == nil
259 # Adjust windows-style uris
260 uri.path.gsub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
261 "/#{$1.downcase}:/"
262 end
263 uri.path.gsub!(/\\/, SLASH)
264 if File.exists?(uri.path) &&
265 File.stat(uri.path).directory?
266 uri.path.gsub!(/\/$/, EMPTY_STR)
267 uri.path = uri.path + '/'
268 end
269
270 # If the path is absolute, set the scheme and host.
271 if uri.path =~ /^\//
272 uri.scheme = "file"
273 uri.host = EMPTY_STR
274 end
275 uri.normalize!
276 end
277
278 return uri
279 end
280
281 ##
282 # Joins several URIs together.
283 #
284 # @param [String, Addressable::URI, #to_str] *uris
285 # The URIs to join.
286 #
287 # @return [Addressable::URI] The joined URI.
288 #
289 # @example
290 # base = "http://example.com/"
291 # uri = Addressable::URI.parse("relative/path")
292 # Addressable::URI.join(base, uri)
293 # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
294 def self.join(*uris)
295 uri_objects = uris.collect do |uri|
296 if !uri.respond_to?(:to_str)
297 raise TypeError, "Can't convert #{uri.class} into String."
298 end
299 uri.kind_of?(self) ? uri : self.parse(uri.to_str)
300 end
301 result = uri_objects.shift.dup
302 for uri in uri_objects
303 result.join!(uri)
304 end
305 return result
306 end
307
308 ##
309 # Percent encodes a URI component.
310 #
311 # @param [String, #to_str] component The URI component to encode.
312 #
313 # @param [String, Regexp] character_class
314 # The characters which are not percent encoded. If a <code>String</code>
315 # is passed, the <code>String</code> must be formatted as a regular
316 # expression character class. (Do not include the surrounding square
317 # brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
318 # everything but the letters 'b' through 'z' and the numbers '0' through
319 # '9' to be percent encoded. If a <code>Regexp</code> is passed, the
320 # value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A set of
321 # useful <code>String</code> values may be found in the
322 # <code>Addressable::URI::CharacterClasses</code> module. The default
323 # value is the reserved plus unreserved character classes specified in
324 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
325 #
326 # @param [Regexp] upcase_encoded
327 # A string of characters that may already be percent encoded, and whose
328 # encodings should be upcased. This allows normalization of percent
329 # encodings for characters not included in the
330 # <code>character_class</code>.
331 #
332 # @return [String] The encoded component.
333 #
334 # @example
335 # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
336 # => "simple%2Fex%61mple"
337 # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
338 # => "simple%2Fex%61mple"
339 # Addressable::URI.encode_component(
340 # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
341 # )
342 # => "simple%2Fexample"
343 def self.encode_component(component, character_class=
344 CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
345 upcase_encoded='')
346 return nil if component.nil?
347
348 begin
349 if component.kind_of?(Symbol) ||
350 component.kind_of?(Numeric) ||
351 component.kind_of?(TrueClass) ||
352 component.kind_of?(FalseClass)
353 component = component.to_s
354 else
355 component = component.to_str
356 end
357 rescue TypeError, NoMethodError
358 raise TypeError, "Can't convert #{component.class} into String."
359 end if !component.is_a? String
360
361 if ![String, Regexp].include?(character_class.class)
362 raise TypeError,
363 "Expected String or Regexp, got #{character_class.inspect}"
364 end
365 if character_class.kind_of?(String)
366 character_class = /[^#{character_class}]/
367 end
368 if component.respond_to?(:force_encoding)
369 # We can't perform regexps on invalid UTF sequences, but
370 # here we need to, so switch to ASCII.
371 component = component.dup
372 component.force_encoding(Encoding::ASCII_8BIT)
373 end
374 # Avoiding gsub! because there are edge cases with frozen strings
375 component = component.gsub(character_class) do |sequence|
376 (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
377 end
378 if upcase_encoded.length > 0
379 component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
380 char.unpack('C*').map { |c| '%02x' % c }.join
381 end.join('|')})/i) { |s| s.upcase }
382 end
383 return component
384 end
385
386 class << self
387 alias_method :encode_component, :encode_component
388 end
389
390 ##
391 # Unencodes any percent encoded characters within a URI component.
392 # This method may be used for unencoding either components or full URIs,
393 # however, it is recommended to use the <code>unencode_component</code>
394 # alias when unencoding components.
395 #
396 # @param [String, Addressable::URI, #to_str] uri
397 # The URI or component to unencode.
398 #
399 # @param [Class] return_type
400 # The type of object to return.
401 # This value may only be set to <code>String</code> or
402 # <code>Addressable::URI</code>. All other values are invalid. Defaults
403 # to <code>String</code>.
404 #
405 # @param [String] leave_encoded
406 # A string of characters to leave encoded. If a percent encoded character
407 # in this list is encountered then it will remain percent encoded.
408 #
409 # @return [String, Addressable::URI]
410 # The unencoded component or URI.
411 # The return type is determined by the <code>return_type</code>
412 # parameter.
413 def self.unencode(uri, return_type=String, leave_encoded='')
414 return nil if uri.nil?
415
416 begin
417 uri = uri.to_str
418 rescue NoMethodError, TypeError
419 raise TypeError, "Can't convert #{uri.class} into String."
420 end if !uri.is_a? String
421 if ![String, ::Addressable::URI].include?(return_type)
422 raise TypeError,
423 "Expected Class (String or Addressable::URI), " +
424 "got #{return_type.inspect}"
425 end
426 uri = uri.dup
427 # Seriously, only use UTF-8. I'm really not kidding!
428 uri.force_encoding("utf-8") if uri.respond_to?(:force_encoding)
429 leave_encoded.force_encoding("utf-8") if leave_encoded.respond_to?(:force_encoding)
430 result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
431 c = sequence[1..3].to_i(16).chr
432 c.force_encoding("utf-8") if c.respond_to?(:force_encoding)
433 leave_encoded.include?(c) ? sequence : c
434 end
435 result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
436 if return_type == String
437 return result
438 elsif return_type == ::Addressable::URI
439 return ::Addressable::URI.parse(result)
440 end
441 end
442
443 class << self
444 alias_method :unescape, :unencode
445 alias_method :unencode_component, :unencode
446 alias_method :unescape_component, :unencode
447 end
448
449
450 ##
451 # Normalizes the encoding of a URI component.
452 #
453 # @param [String, #to_str] component The URI component to encode.
454 #
455 # @param [String, Regexp] character_class
456 # The characters which are not percent encoded. If a <code>String</code>
457 # is passed, the <code>String</code> must be formatted as a regular
458 # expression character class. (Do not include the surrounding square
459 # brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
460 # everything but the letters 'b' through 'z' and the numbers '0'
461 # through '9' to be percent encoded. If a <code>Regexp</code> is passed,
462 # the value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
463 # set of useful <code>String</code> values may be found in the
464 # <code>Addressable::URI::CharacterClasses</code> module. The default
465 # value is the reserved plus unreserved character classes specified in
466 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
467 #
468 # @param [String] leave_encoded
469 # When <code>character_class</code> is a <code>String</code> then
470 # <code>leave_encoded</code> is a string of characters that should remain
471 # percent encoded while normalizing the component; if they appear percent
472 # encoded in the original component, then they will be upcased ("%2f"
473 # normalized to "%2F") but otherwise left alone.
474 #
475 # @return [String] The normalized component.
476 #
477 # @example
478 # Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z")
479 # => "simple%2Fex%61mple"
480 # Addressable::URI.normalize_component(
481 # "simpl%65/%65xampl%65", /[^b-zB-Z]/
482 # )
483 # => "simple%2Fex%61mple"
484 # Addressable::URI.normalize_component(
485 # "simpl%65/%65xampl%65",
486 # Addressable::URI::CharacterClasses::UNRESERVED
487 # )
488 # => "simple%2Fexample"
489 # Addressable::URI.normalize_component(
490 # "one%20two%2fthree%26four",
491 # "0-9a-zA-Z &/",
492 # "/"
493 # )
494 # => "one two%2Fthree&four"
495 def self.normalize_component(component, character_class=
496 CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
497 leave_encoded='')
498 return nil if component.nil?
499
500 begin
501 component = component.to_str
502 rescue NoMethodError, TypeError
503 raise TypeError, "Can't convert #{component.class} into String."
504 end if !component.is_a? String
505
506 if ![String, Regexp].include?(character_class.class)
507 raise TypeError,
508 "Expected String or Regexp, got #{character_class.inspect}"
509 end
510 if character_class.kind_of?(String)
511 leave_re = if leave_encoded.length > 0
512 character_class = "#{character_class}%" unless character_class.include?('%')
513
514 "|%(?!#{leave_encoded.chars.map do |char|
515 seq = char.unpack('C*').map { |c| '%02x' % c }.join
516 [seq.upcase, seq.downcase]
517 end.flatten.join('|')})"
518 end
519
520 character_class = /[^#{character_class}]#{leave_re}/
521 end
522 if component.respond_to?(:force_encoding)
523 # We can't perform regexps on invalid UTF sequences, but
524 # here we need to, so switch to ASCII.
525 component = component.dup
526 component.force_encoding(Encoding::ASCII_8BIT)
527 end
528 unencoded = self.unencode_component(component, String, leave_encoded)
529 begin
530 encoded = self.encode_component(
531 Addressable::IDNA.unicode_normalize_kc(unencoded),
532 character_class,
533 leave_encoded
534 )
535 rescue ArgumentError
536 encoded = self.encode_component(unencoded)
537 end
538 if encoded.respond_to?(:force_encoding)
539 encoded.force_encoding(Encoding::UTF_8)
540 end
541 return encoded
542 end
543
544 ##
545 # Percent encodes any special characters in the URI.
546 #
547 # @param [String, Addressable::URI, #to_str] uri
548 # The URI to encode.
549 #
550 # @param [Class] return_type
551 # The type of object to return.
552 # This value may only be set to <code>String</code> or
553 # <code>Addressable::URI</code>. All other values are invalid. Defaults
554 # to <code>String</code>.
555 #
556 # @return [String, Addressable::URI]
557 # The encoded URI.
558 # The return type is determined by the <code>return_type</code>
559 # parameter.
560 def self.encode(uri, return_type=String)
561 return nil if uri.nil?
562
563 begin
564 uri = uri.to_str
565 rescue NoMethodError, TypeError
566 raise TypeError, "Can't convert #{uri.class} into String."
567 end if !uri.is_a? String
568
569 if ![String, ::Addressable::URI].include?(return_type)
570 raise TypeError,
571 "Expected Class (String or Addressable::URI), " +
572 "got #{return_type.inspect}"
573 end
574 uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
575 encoded_uri = Addressable::URI.new(
576 :scheme => self.encode_component(uri_object.scheme,
577 Addressable::URI::CharacterClasses::SCHEME),
578 :authority => self.encode_component(uri_object.authority,
579 Addressable::URI::CharacterClasses::AUTHORITY),
580 :path => self.encode_component(uri_object.path,
581 Addressable::URI::CharacterClasses::PATH),
582 :query => self.encode_component(uri_object.query,
583 Addressable::URI::CharacterClasses::QUERY),
584 :fragment => self.encode_component(uri_object.fragment,
585 Addressable::URI::CharacterClasses::FRAGMENT)
586 )
587 if return_type == String
588 return encoded_uri.to_s
589 elsif return_type == ::Addressable::URI
590 return encoded_uri
591 end
592 end
593
594 class << self
595 alias_method :escape, :encode
596 end
597
598 ##
599 # Normalizes the encoding of a URI. Characters within a hostname are
600 # not percent encoded to allow for internationalized domain names.
601 #
602 # @param [String, Addressable::URI, #to_str] uri
603 # The URI to encode.
604 #
605 # @param [Class] return_type
606 # The type of object to return.
607 # This value may only be set to <code>String</code> or
608 # <code>Addressable::URI</code>. All other values are invalid. Defaults
609 # to <code>String</code>.
610 #
611 # @return [String, Addressable::URI]
612 # The encoded URI.
613 # The return type is determined by the <code>return_type</code>
614 # parameter.
615 def self.normalized_encode(uri, return_type=String)
616 begin
617 uri = uri.to_str
618 rescue NoMethodError, TypeError
619 raise TypeError, "Can't convert #{uri.class} into String."
620 end if !uri.is_a? String
621
622 if ![String, ::Addressable::URI].include?(return_type)
623 raise TypeError,
624 "Expected Class (String or Addressable::URI), " +
625 "got #{return_type.inspect}"
626 end
627 uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
628 components = {
629 :scheme => self.unencode_component(uri_object.scheme),
630 :user => self.unencode_component(uri_object.user),
631 :password => self.unencode_component(uri_object.password),
632 :host => self.unencode_component(uri_object.host),
633 :port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
634 :path => self.unencode_component(uri_object.path),
635 :query => self.unencode_component(uri_object.query),
636 :fragment => self.unencode_component(uri_object.fragment)
637 }
638 components.each do |key, value|
639 if value != nil
640 begin
641 components[key] =
642 Addressable::IDNA.unicode_normalize_kc(value.to_str)
643 rescue ArgumentError
644 # Likely a malformed UTF-8 character, skip unicode normalization
645 components[key] = value.to_str
646 end
647 end
648 end
649 encoded_uri = Addressable::URI.new(
650 :scheme => self.encode_component(components[:scheme],
651 Addressable::URI::CharacterClasses::SCHEME),
652 :user => self.encode_component(components[:user],
653 Addressable::URI::CharacterClasses::UNRESERVED),
654 :password => self.encode_component(components[:password],
655 Addressable::URI::CharacterClasses::UNRESERVED),
656 :host => components[:host],
657 :port => components[:port],
658 :path => self.encode_component(components[:path],
659 Addressable::URI::CharacterClasses::PATH),
660 :query => self.encode_component(components[:query],
661 Addressable::URI::CharacterClasses::QUERY),
662 :fragment => self.encode_component(components[:fragment],
663 Addressable::URI::CharacterClasses::FRAGMENT)
664 )
665 if return_type == String
666 return encoded_uri.to_s
667 elsif return_type == ::Addressable::URI
668 return encoded_uri
669 end
670 end
671
672 ##
673 # Encodes a set of key/value pairs according to the rules for the
674 # <code>application/x-www-form-urlencoded</code> MIME type.
675 #
676 # @param [#to_hash, #to_ary] form_values
677 # The form values to encode.
678 #
679 # @param [TrueClass, FalseClass] sort
680 # Sort the key/value pairs prior to encoding.
681 # Defaults to <code>false</code>.
682 #
683 # @return [String]
684 # The encoded value.
685 def self.form_encode(form_values, sort=false)
686 if form_values.respond_to?(:to_hash)
687 form_values = form_values.to_hash.to_a
688 elsif form_values.respond_to?(:to_ary)
689 form_values = form_values.to_ary
690 else
691 raise TypeError, "Can't convert #{form_values.class} into Array."
692 end
693
694 form_values = form_values.inject([]) do |accu, (key, value)|
695 if value.kind_of?(Array)
696 value.each do |v|
697 accu << [key.to_s, v.to_s]
698 end
699 else
700 accu << [key.to_s, value.to_s]
701 end
702 accu
703 end
704
705 if sort
706 # Useful for OAuth and optimizing caching systems
707 form_values = form_values.sort
708 end
709 escaped_form_values = form_values.map do |(key, value)|
710 # Line breaks are CRLF pairs
711 [
712 self.encode_component(
713 key.gsub(/(\r\n|\n|\r)/, "\r\n"),
714 CharacterClasses::UNRESERVED
715 ).gsub("%20", "+"),
716 self.encode_component(
717 value.gsub(/(\r\n|\n|\r)/, "\r\n"),
718 CharacterClasses::UNRESERVED
719 ).gsub("%20", "+")
720 ]
721 end
722 return (escaped_form_values.map do |(key, value)|
723 "#{key}=#{value}"
724 end).join("&")
725 end
726
727 ##
728 # Decodes a <code>String</code> according to the rules for the
729 # <code>application/x-www-form-urlencoded</code> MIME type.
730 #
731 # @param [String, #to_str] encoded_value
732 # The form values to decode.
733 #
734 # @return [Array]
735 # The decoded values.
736 # This is not a <code>Hash</code> because of the possibility for
737 # duplicate keys.
738 def self.form_unencode(encoded_value)
739 if !encoded_value.respond_to?(:to_str)
740 raise TypeError, "Can't convert #{encoded_value.class} into String."
741 end
742 encoded_value = encoded_value.to_str
743 split_values = encoded_value.split("&").map do |pair|
744 pair.split("=", 2)
745 end
746 return split_values.map do |(key, value)|
747 [
748 key ? self.unencode_component(
749 key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil,
750 value ? (self.unencode_component(
751 value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil
752 ]
753 end
754 end
755
756 ##
757 # Creates a new uri object from component parts.
758 #
759 # @option [String, #to_str] scheme The scheme component.
760 # @option [String, #to_str] user The user component.
761 # @option [String, #to_str] password The password component.
762 # @option [String, #to_str] userinfo
763 # The userinfo component. If this is supplied, the user and password
764 # components must be omitted.
765 # @option [String, #to_str] host The host component.
766 # @option [String, #to_str] port The port component.
767 # @option [String, #to_str] authority
768 # The authority component. If this is supplied, the user, password,
769 # userinfo, host, and port components must be omitted.
770 # @option [String, #to_str] path The path component.
771 # @option [String, #to_str] query The query component.
772 # @option [String, #to_str] fragment The fragment component.
773 #
774 # @return [Addressable::URI] The constructed URI object.
775 def initialize(options={})
776 if options.has_key?(:authority)
777 if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
778 raise ArgumentError,
779 "Cannot specify both an authority and any of the components " +
780 "within the authority."
781 end
782 end
783 if options.has_key?(:userinfo)
784 if (options.keys & [:user, :password]).any?
785 raise ArgumentError,
786 "Cannot specify both a userinfo and either the user or password."
787 end
788 end
789
790 self.defer_validation do
791 # Bunch of crazy logic required because of the composite components
792 # like userinfo and authority.
793 self.scheme = options[:scheme] if options[:scheme]
794 self.user = options[:user] if options[:user]
795 self.password = options[:password] if options[:password]
796 self.userinfo = options[:userinfo] if options[:userinfo]
797 self.host = options[:host] if options[:host]
798 self.port = options[:port] if options[:port]
799 self.authority = options[:authority] if options[:authority]
800 self.path = options[:path] if options[:path]
801 self.query = options[:query] if options[:query]
802 self.query_values = options[:query_values] if options[:query_values]
803 self.fragment = options[:fragment] if options[:fragment]
804 end
805 end
806
807 ##
808 # Freeze URI, initializing instance variables.
809 #
810 # @return [Addressable::URI] The frozen URI object.
811 def freeze
812 self.normalized_scheme
813 self.normalized_user
814 self.normalized_password
815 self.normalized_userinfo
816 self.normalized_host
817 self.normalized_port
818 self.normalized_authority
819 self.normalized_site
820 self.normalized_path
821 self.normalized_query
822 self.normalized_fragment
823 self.hash
824 super
825 end
826
827 ##
828 # The scheme component for this URI.
829 #
830 # @return [String] The scheme component.
831 def scheme
832 return instance_variable_defined?(:@scheme) ? @scheme : nil
833 end
834
835 ##
836 # The scheme component for this URI, normalized.
837 #
838 # @return [String] The scheme component, normalized.
839 def normalized_scheme
840 self.scheme && @normalized_scheme ||= (begin
841 if self.scheme =~ /^\s*ssh\+svn\s*$/i
842 "svn+ssh"
843 else
844 Addressable::URI.normalize_component(
845 self.scheme.strip.downcase,
846 Addressable::URI::CharacterClasses::SCHEME
847 )
848 end
849 end)
850 end
851
852 ##
853 # Sets the scheme component for this URI.
854 #
855 # @param [String, #to_str] new_scheme The new scheme component.
856 def scheme=(new_scheme)
857 if new_scheme && !new_scheme.respond_to?(:to_str)
858 raise TypeError, "Can't convert #{new_scheme.class} into String."
859 elsif new_scheme
860 new_scheme = new_scheme.to_str
861 end
862 if new_scheme && new_scheme !~ /[a-z][a-z0-9\.\+\-]*/i
863 raise InvalidURIError, "Invalid scheme format."
864 end
865 @scheme = new_scheme
866 @scheme = nil if @scheme.to_s.strip.empty?
867
868 # Reset dependant values
869 @normalized_scheme = nil
870 @uri_string = nil
871 @hash = nil
872
873 # Ensure we haven't created an invalid URI
874 validate()
875 end
876
877 ##
878 # The user component for this URI.
879 #
880 # @return [String] The user component.
881 def user
882 return instance_variable_defined?(:@user) ? @user : nil
883 end
884
885 ##
886 # The user component for this URI, normalized.
887 #
888 # @return [String] The user component, normalized.
889 def normalized_user
890 self.user && @normalized_user ||= (begin
891 if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
892 (!self.password || self.password.strip.empty?)
893 nil
894 else
895 Addressable::URI.normalize_component(
896 self.user.strip,
897 Addressable::URI::CharacterClasses::UNRESERVED
898 )
899 end
900 end)
901 end
902
903 ##
904 # Sets the user component for this URI.
905 #
906 # @param [String, #to_str] new_user The new user component.
907 def user=(new_user)
908 if new_user && !new_user.respond_to?(:to_str)
909 raise TypeError, "Can't convert #{new_user.class} into String."
910 end
911 @user = new_user ? new_user.to_str : nil
912
913 # You can't have a nil user with a non-nil password
914 if password != nil
915 @user = EMPTY_STR if @user.nil?
916 end
917
918 # Reset dependant values
919 @userinfo = nil
920 @normalized_userinfo = nil
921 @authority = nil
922 @normalized_user = nil
923 @uri_string = nil
924 @hash = nil
925
926 # Ensure we haven't created an invalid URI
927 validate()
928 end
929
930 ##
931 # The password component for this URI.
932 #
933 # @return [String] The password component.
934 def password
935 return instance_variable_defined?(:@password) ? @password : nil
936 end
937
938 ##
939 # The password component for this URI, normalized.
940 #
941 # @return [String] The password component, normalized.
942 def normalized_password
943 self.password && @normalized_password ||= (begin
944 if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
945 (!self.user || self.user.strip.empty?)
946 nil
947 else
948 Addressable::URI.normalize_component(
949 self.password.strip,
950 Addressable::URI::CharacterClasses::UNRESERVED
951 )
952 end
953 end)
954 end
955
956 ##
957 # Sets the password component for this URI.
958 #
959 # @param [String, #to_str] new_password The new password component.
960 def password=(new_password)
961 if new_password && !new_password.respond_to?(:to_str)
962 raise TypeError, "Can't convert #{new_password.class} into String."
963 end
964 @password = new_password ? new_password.to_str : nil
965
966 # You can't have a nil user with a non-nil password
967 @password ||= nil
968 @user ||= nil
969 if @password != nil
970 @user = EMPTY_STR if @user.nil?
971 end
972
973 # Reset dependant values
974 @userinfo = nil
975 @normalized_userinfo = nil
976 @authority = nil
977 @normalized_password = nil
978 @uri_string = nil
979 @hash = nil
980
981 # Ensure we haven't created an invalid URI
982 validate()
983 end
984
985 ##
986 # The userinfo component for this URI.
987 # Combines the user and password components.
988 #
989 # @return [String] The userinfo component.
990 def userinfo
991 current_user = self.user
992 current_password = self.password
993 (current_user || current_password) && @userinfo ||= (begin
994 if current_user && current_password
995 "#{current_user}:#{current_password}"
996 elsif current_user && !current_password
997 "#{current_user}"
998 end
999 end)
1000 end
1001
1002 ##
1003 # The userinfo component for this URI, normalized.
1004 #
1005 # @return [String] The userinfo component, normalized.
1006 def normalized_userinfo
1007 self.userinfo && @normalized_userinfo ||= (begin
1008 current_user = self.normalized_user
1009 current_password = self.normalized_password
1010 if !current_user && !current_password
1011 nil
1012 elsif current_user && current_password
1013 "#{current_user}:#{current_password}"
1014 elsif current_user && !current_password
1015 "#{current_user}"
1016 end
1017 end)
1018 end
1019
1020 ##
1021 # Sets the userinfo component for this URI.
1022 #
1023 # @param [String, #to_str] new_userinfo The new userinfo component.
1024 def userinfo=(new_userinfo)
1025 if new_userinfo && !new_userinfo.respond_to?(:to_str)
1026 raise TypeError, "Can't convert #{new_userinfo.class} into String."
1027 end
1028 new_user, new_password = if new_userinfo
1029 [
1030 new_userinfo.to_str.strip[/^(.*):/, 1],
1031 new_userinfo.to_str.strip[/:(.*)$/, 1]
1032 ]
1033 else
1034 [nil, nil]
1035 end
1036
1037 # Password assigned first to ensure validity in case of nil
1038 self.password = new_password
1039 self.user = new_user
1040
1041 # Reset dependant values
1042 @authority = nil
1043 @uri_string = nil
1044 @hash = nil
1045
1046 # Ensure we haven't created an invalid URI
1047 validate()
1048 end
1049
1050 ##
1051 # The host component for this URI.
1052 #
1053 # @return [String] The host component.
1054 def host
1055 return instance_variable_defined?(:@host) ? @host : nil
1056 end
1057
1058 ##
1059 # The host component for this URI, normalized.
1060 #
1061 # @return [String] The host component, normalized.
1062 def normalized_host
1063 self.host && @normalized_host ||= (begin
1064 if !self.host.strip.empty?
1065 result = ::Addressable::IDNA.to_ascii(
1066 URI.unencode_component(self.host.strip.downcase)
1067 )
1068 if result =~ /[^\.]\.$/
1069 # Single trailing dots are unnecessary.
1070 result = result[0...-1]
1071 end
1072 result
1073 else
1074 EMPTY_STR
1075 end
1076 end)
1077 end
1078
1079 ##
1080 # Sets the host component for this URI.
1081 #
1082 # @param [String, #to_str] new_host The new host component.
1083 def host=(new_host)
1084 if new_host && !new_host.respond_to?(:to_str)
1085 raise TypeError, "Can't convert #{new_host.class} into String."
1086 end
1087 @host = new_host ? new_host.to_str : nil
1088
1089 unreserved = CharacterClasses::UNRESERVED
1090 sub_delims = CharacterClasses::SUB_DELIMS
1091 if @host != nil && (@host =~ /[<>{}\/\?\#\@]/ ||
1092 (@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
1093 Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
1094 raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
1095 end
1096
1097 # Reset dependant values
1098 @authority = nil
1099 @normalized_host = nil
1100 @uri_string = nil
1101 @hash = nil
1102
1103 # Ensure we haven't created an invalid URI
1104 validate()
1105 end
1106
1107 ##
1108 # This method is same as URI::Generic#host except
1109 # brackets for IPv6 (and 'IPvFuture') addresses are removed.
1110 #
1111 # @see Addressable::URI#host
1112 #
1113 # @return [String] The hostname for this URI.
1114 def hostname
1115 v = self.host
1116 /\A\[(.*)\]\z/ =~ v ? $1 : v
1117 end
1118
1119 ##
1120 # This method is same as URI::Generic#host= except
1121 # the argument can be a bare IPv6 address (or 'IPvFuture').
1122 #
1123 # @see Addressable::URI#host=
1124 #
1125 # @param [String, #to_str] new_hostname The new hostname for this URI.
1126 def hostname=(new_hostname)
1127 if new_hostname && !new_hostname.respond_to?(:to_str)
1128 raise TypeError, "Can't convert #{new_hostname.class} into String."
1129 end
1130 v = new_hostname ? new_hostname.to_str : nil
1131 v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
1132 self.host = v
1133 end
1134
1135 ##
1136 # The authority component for this URI.
1137 # Combines the user, password, host, and port components.
1138 #
1139 # @return [String] The authority component.
1140 def authority
1141 self.host && @authority ||= (begin
1142 authority = ""
1143 if self.userinfo != nil
1144 authority << "#{self.userinfo}@"
1145 end
1146 authority << self.host
1147 if self.port != nil
1148 authority << ":#{self.port}"
1149 end
1150 authority
1151 end)
1152 end
1153
1154 ##
1155 # The authority component for this URI, normalized.
1156 #
1157 # @return [String] The authority component, normalized.
1158 def normalized_authority
1159 self.authority && @normalized_authority ||= (begin
1160 authority = ""
1161 if self.normalized_userinfo != nil
1162 authority << "#{self.normalized_userinfo}@"
1163 end
1164 authority << self.normalized_host
1165 if self.normalized_port != nil
1166 authority << ":#{self.normalized_port}"
1167 end
1168 authority
1169 end)
1170 end
1171
1172 ##
1173 # Sets the authority component for this URI.
1174 #
1175 # @param [String, #to_str] new_authority The new authority component.
1176 def authority=(new_authority)
1177 if new_authority
1178 if !new_authority.respond_to?(:to_str)
1179 raise TypeError, "Can't convert #{new_authority.class} into String."
1180 end
1181 new_authority = new_authority.to_str
1182 new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
1183 if new_userinfo
1184 new_user = new_userinfo.strip[/^([^:]*):?/, 1]
1185 new_password = new_userinfo.strip[/:(.*)$/, 1]
1186 end
1187 new_host = new_authority.gsub(
1188 /^([^\[\]]*)@/, EMPTY_STR
1189 ).gsub(
1190 /:([^:@\[\]]*?)$/, EMPTY_STR
1191 )
1192 new_port =
1193 new_authority[/:([^:@\[\]]*?)$/, 1]
1194 end
1195
1196 # Password assigned first to ensure validity in case of nil
1197 self.password = defined?(new_password) ? new_password : nil
1198 self.user = defined?(new_user) ? new_user : nil
1199 self.host = defined?(new_host) ? new_host : nil
1200 self.port = defined?(new_port) ? new_port : nil
1201
1202 # Reset dependant values
1203 @userinfo = nil
1204 @normalized_userinfo = nil
1205 @uri_string = nil
1206 @hash = nil
1207
1208 # Ensure we haven't created an invalid URI
1209 validate()
1210 end
1211
1212 ##
1213 # The origin for this URI, serialized to ASCII, as per
1214 # RFC 6454, section 6.2.
1215 #
1216 # @return [String] The serialized origin.
1217 def origin
1218 return (if self.scheme && self.authority
1219 if self.normalized_port
1220 (
1221 "#{self.normalized_scheme}://#{self.normalized_host}" +
1222 ":#{self.normalized_port}"
1223 )
1224 else
1225 "#{self.normalized_scheme}://#{self.normalized_host}"
1226 end
1227 else
1228 "null"
1229 end)
1230 end
1231
1232 # Returns an array of known ip-based schemes. These schemes typically
1233 # use a similar URI form:
1234 # <code>//<user>:<password>@<host>:<port>/<url-path></code>
1235 def self.ip_based_schemes
1236 return self.port_mapping.keys
1237 end
1238
1239 # Returns a hash of common IP-based schemes and their default port
1240 # numbers. Adding new schemes to this hash, as necessary, will allow
1241 # for better URI normalization.
1242 def self.port_mapping
1243 PORT_MAPPING
1244 end
1245
1246 ##
1247 # The port component for this URI.
1248 # This is the port number actually given in the URI. This does not
1249 # infer port numbers from default values.
1250 #
1251 # @return [Integer] The port component.
1252 def port
1253 return instance_variable_defined?(:@port) ? @port : nil
1254 end
1255
1256 ##
1257 # The port component for this URI, normalized.
1258 #
1259 # @return [Integer] The port component, normalized.
1260 def normalized_port
1261 if URI.port_mapping[self.normalized_scheme] == self.port
1262 nil
1263 else
1264 self.port
1265 end
1266 end
1267
1268 ##
1269 # Sets the port component for this URI.
1270 #
1271 # @param [String, Integer, #to_s] new_port The new port component.
1272 def port=(new_port)
1273 if new_port != nil && new_port.respond_to?(:to_str)
1274 new_port = Addressable::URI.unencode_component(new_port.to_str)
1275 end
1276 if new_port != nil && !(new_port.to_s =~ /^\d+$/)
1277 raise InvalidURIError,
1278 "Invalid port number: #{new_port.inspect}"
1279 end
1280
1281 @port = new_port.to_s.to_i
1282 @port = nil if @port == 0
1283
1284 # Reset dependant values
1285 @authority = nil
1286 @normalized_port = nil
1287 @uri_string = nil
1288 @hash = nil
1289
1290 # Ensure we haven't created an invalid URI
1291 validate()
1292 end
1293
1294 ##
1295 # The inferred port component for this URI.
1296 # This method will normalize to the default port for the URI's scheme if
1297 # the port isn't explicitly specified in the URI.
1298 #
1299 # @return [Integer] The inferred port component.
1300 def inferred_port
1301 if self.port.to_i == 0
1302 self.default_port
1303 else
1304 self.port.to_i
1305 end
1306 end
1307
1308 ##
1309 # The default port for this URI's scheme.
1310 # This method will always returns the default port for the URI's scheme
1311 # regardless of the presence of an explicit port in the URI.
1312 #
1313 # @return [Integer] The default port.
1314 def default_port
1315 URI.port_mapping[self.scheme.strip.downcase] if self.scheme
1316 end
1317
1318 ##
1319 # The combination of components that represent a site.
1320 # Combines the scheme, user, password, host, and port components.
1321 # Primarily useful for HTTP and HTTPS.
1322 #
1323 # For example, <code>"http://example.com/path?query"</code> would have a
1324 # <code>site</code> value of <code>"http://example.com"</code>.
1325 #
1326 # @return [String] The components that identify a site.
1327 def site
1328 (self.scheme || self.authority) && @site ||= (begin
1329 site_string = ""
1330 site_string << "#{self.scheme}:" if self.scheme != nil
1331 site_string << "//#{self.authority}" if self.authority != nil
1332 site_string
1333 end)
1334 end
1335
1336 ##
1337 # The normalized combination of components that represent a site.
1338 # Combines the scheme, user, password, host, and port components.
1339 # Primarily useful for HTTP and HTTPS.
1340 #
1341 # For example, <code>"http://example.com/path?query"</code> would have a
1342 # <code>site</code> value of <code>"http://example.com"</code>.
1343 #
1344 # @return [String] The normalized components that identify a site.
1345 def normalized_site
1346 self.site && @normalized_site ||= (begin
1347 site_string = ""
1348 if self.normalized_scheme != nil
1349 site_string << "#{self.normalized_scheme}:"
1350 end
1351 if self.normalized_authority != nil
1352 site_string << "//#{self.normalized_authority}"
1353 end
1354 site_string
1355 end)
1356 end
1357
1358 ##
1359 # Sets the site value for this URI.
1360 #
1361 # @param [String, #to_str] new_site The new site value.
1362 def site=(new_site)
1363 if new_site
1364 if !new_site.respond_to?(:to_str)
1365 raise TypeError, "Can't convert #{new_site.class} into String."
1366 end
1367 new_site = new_site.to_str
1368 # These two regular expressions derived from the primary parsing
1369 # expression
1370 self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1]
1371 self.authority = new_site[
1372 /^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1
1373 ]
1374 else
1375 self.scheme = nil
1376 self.authority = nil
1377 end
1378 end
1379
1380 ##
1381 # The path component for this URI.
1382 #
1383 # @return [String] The path component.
1384 def path
1385 return instance_variable_defined?(:@path) ? @path : EMPTY_STR
1386 end
1387
1388 NORMPATH = /^(?!\/)[^\/:]*:.*$/
1389 ##
1390 # The path component for this URI, normalized.
1391 #
1392 # @return [String] The path component, normalized.
1393 def normalized_path
1394 @normalized_path ||= (begin
1395 path = self.path.to_s
1396 if self.scheme == nil && path =~ NORMPATH
1397 # Relative paths with colons in the first segment are ambiguous.
1398 path = path.sub(":", "%2F")
1399 end
1400 # String#split(delimeter, -1) uses the more strict splitting behavior
1401 # found by default in Python.
1402 result = (path.strip.split(SLASH, -1).map do |segment|
1403 Addressable::URI.normalize_component(
1404 segment,
1405 Addressable::URI::CharacterClasses::PCHAR
1406 )
1407 end).join(SLASH)
1408
1409 result = URI.normalize_path(result)
1410 if result.empty? &&
1411 ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
1412 result = SLASH
1413 end
1414 result
1415 end)
1416 end
1417
1418 ##
1419 # Sets the path component for this URI.
1420 #
1421 # @param [String, #to_str] new_path The new path component.
1422 def path=(new_path)
1423 if new_path && !new_path.respond_to?(:to_str)
1424 raise TypeError, "Can't convert #{new_path.class} into String."
1425 end
1426 @path = (new_path || EMPTY_STR).to_str
1427 if !@path.empty? && @path[0..0] != SLASH && host != nil
1428 @path = "/#{@path}"
1429 end
1430
1431 # Reset dependant values
1432 @normalized_path = nil
1433 @uri_string = nil
1434 @hash = nil
1435 end
1436
1437 ##
1438 # The basename, if any, of the file in the path component.
1439 #
1440 # @return [String] The path's basename.
1441 def basename
1442 # Path cannot be nil
1443 return File.basename(self.path).gsub(/;[^\/]*$/, EMPTY_STR)
1444 end
1445
1446 ##
1447 # The extname, if any, of the file in the path component.
1448 # Empty string if there is no extension.
1449 #
1450 # @return [String] The path's extname.
1451 def extname
1452 return nil unless self.path
1453 return File.extname(self.basename)
1454 end
1455
1456 ##
1457 # The query component for this URI.
1458 #
1459 # @return [String] The query component.
1460 def query
1461 return instance_variable_defined?(:@query) ? @query : nil
1462 end
1463
1464 ##
1465 # The query component for this URI, normalized.
1466 #
1467 # @return [String] The query component, normalized.
1468 def normalized_query(*flags)
1469 modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1470 # Make sure possible key-value pair delimiters are escaped.
1471 modified_query_class.sub!("\\&", "").sub!("\\;", "")
1472 pairs = (self.query || "").split("&", -1)
1473 pairs.sort! if flags.include?(:sorted)
1474 component = (pairs.map do |pair|
1475 Addressable::URI.normalize_component(pair, modified_query_class, "+")
1476 end).join("&")
1477 component == "" ? nil : component
1478 end
1479
1480 ##
1481 # Sets the query component for this URI.
1482 #
1483 # @param [String, #to_str] new_query The new query component.
1484 def query=(new_query)
1485 if new_query && !new_query.respond_to?(:to_str)
1486 raise TypeError, "Can't convert #{new_query.class} into String."
1487 end
1488 @query = new_query ? new_query.to_str : nil
1489
1490 # Reset dependant values
1491 @normalized_query = nil
1492 @uri_string = nil
1493 @hash = nil
1494 end
1495
1496 ##
1497 # Converts the query component to a Hash value.
1498 #
1499 # @param [Class] return_type The return type desired. Value must be either
1500 # `Hash` or `Array`.
1501 #
1502 # @return [Hash, Array] The query string parsed as a Hash or Array object.
1503 #
1504 # @example
1505 # Addressable::URI.parse("?one=1&two=2&three=3").query_values
1506 # #=> {"one" => "1", "two" => "2", "three" => "3"}
1507 # Addressable::URI.parse("?one=two&one=three").query_values(Array)
1508 # #=> [["one", "two"], ["one", "three"]]
1509 # Addressable::URI.parse("?one=two&one=three").query_values(Hash)
1510 # #=> {"one" => "three"}
1511 def query_values(return_type=Hash)
1512 empty_accumulator = Array == return_type ? [] : {}
1513 if return_type != Hash && return_type != Array
1514 raise ArgumentError, "Invalid return type. Must be Hash or Array."
1515 end
1516 return nil if self.query == nil
1517 split_query = (self.query.split("&").map do |pair|
1518 pair.split("=", 2) if pair && !pair.empty?
1519 end).compact
1520 return split_query.inject(empty_accumulator.dup) do |accu, pair|
1521 # I'd rather use key/value identifiers instead of array lookups,
1522 # but in this case I really want to maintain the exact pair structure,
1523 # so it's best to make all changes in-place.
1524 pair[0] = URI.unencode_component(pair[0])
1525 if pair[1].respond_to?(:to_str)
1526 # I loathe the fact that I have to do this. Stupid HTML 4.01.
1527 # Treating '+' as a space was just an unbelievably bad idea.
1528 # There was nothing wrong with '%20'!
1529 # If it ain't broke, don't fix it!
1530 pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
1531 end
1532 if return_type == Hash
1533 accu[pair[0]] = pair[1]
1534 else
1535 accu << pair
1536 end
1537 accu
1538 end
1539 end
1540
1541 ##
1542 # Sets the query component for this URI from a Hash object.
1543 # An empty Hash or Array will result in an empty query string.
1544 #
1545 # @param [Hash, #to_hash, Array] new_query_values The new query values.
1546 #
1547 # @example
1548 # uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
1549 # uri.query
1550 # # => "a=a&b=c&b=d&b=e"
1551 # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
1552 # uri.query
1553 # # => "a=a&b=c&b=d&b=e"
1554 # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
1555 # uri.query
1556 # # => "a=a&b=c&b=d&b=e"
1557 # uri.query_values = [['flag'], ['key', 'value']]
1558 # uri.query
1559 # # => "flag&key=value"
1560 def query_values=(new_query_values)
1561 if new_query_values == nil
1562 self.query = nil
1563 return nil
1564 end
1565
1566 if !new_query_values.is_a?(Array)
1567 if !new_query_values.respond_to?(:to_hash)
1568 raise TypeError,
1569 "Can't convert #{new_query_values.class} into Hash."
1570 end
1571 new_query_values = new_query_values.to_hash
1572 new_query_values = new_query_values.map do |key, value|
1573 key = key.to_s if key.kind_of?(Symbol)
1574 [key, value]
1575 end
1576 # Useful default for OAuth and caching.
1577 # Only to be used for non-Array inputs. Arrays should preserve order.
1578 new_query_values.sort!
1579 end
1580
1581 # new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
1582 buffer = ""
1583 new_query_values.each do |key, value|
1584 encoded_key = URI.encode_component(
1585 key, CharacterClasses::UNRESERVED
1586 )
1587 if value == nil
1588 buffer << "#{encoded_key}&"
1589 elsif value.kind_of?(Array)
1590 value.each do |sub_value|
1591 encoded_value = URI.encode_component(
1592 sub_value, CharacterClasses::UNRESERVED
1593 )
1594 buffer << "#{encoded_key}=#{encoded_value}&"
1595 end
1596 else
1597 encoded_value = URI.encode_component(
1598 value, CharacterClasses::UNRESERVED
1599 )
1600 buffer << "#{encoded_key}=#{encoded_value}&"
1601 end
1602 end
1603 self.query = buffer.chop
1604 end
1605
1606 ##
1607 # The HTTP request URI for this URI. This is the path and the
1608 # query string.
1609 #
1610 # @return [String] The request URI required for an HTTP request.
1611 def request_uri
1612 return nil if self.absolute? && self.scheme !~ /^https?$/
1613 return (
1614 (!self.path.empty? ? self.path : SLASH) +
1615 (self.query ? "?#{self.query}" : EMPTY_STR)
1616 )
1617 end
1618
1619 ##
1620 # Sets the HTTP request URI for this URI.
1621 #
1622 # @param [String, #to_str] new_request_uri The new HTTP request URI.
1623 def request_uri=(new_request_uri)
1624 if !new_request_uri.respond_to?(:to_str)
1625 raise TypeError, "Can't convert #{new_request_uri.class} into String."
1626 end
1627 if self.absolute? && self.scheme !~ /^https?$/
1628 raise InvalidURIError,
1629 "Cannot set an HTTP request URI for a non-HTTP URI."
1630 end
1631 new_request_uri = new_request_uri.to_str
1632 path_component = new_request_uri[/^([^\?]*)\?(?:.*)$/, 1]
1633 query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
1634 path_component = path_component.to_s
1635 path_component = (!path_component.empty? ? path_component : SLASH)
1636 self.path = path_component
1637 self.query = query_component
1638
1639 # Reset dependant values
1640 @uri_string = nil
1641 @hash = nil
1642 end
1643
1644 ##
1645 # The fragment component for this URI.
1646 #
1647 # @return [String] The fragment component.
1648 def fragment
1649 return instance_variable_defined?(:@fragment) ? @fragment : nil
1650 end
1651
1652 ##
1653 # The fragment component for this URI, normalized.
1654 #
1655 # @return [String] The fragment component, normalized.
1656 def normalized_fragment
1657 self.fragment && @normalized_fragment ||= (begin
1658 component = Addressable::URI.normalize_component(
1659 self.fragment,
1660 Addressable::URI::CharacterClasses::FRAGMENT
1661 )
1662 component == "" ? nil : component
1663 end)
1664 end
1665
1666 ##
1667 # Sets the fragment component for this URI.
1668 #
1669 # @param [String, #to_str] new_fragment The new fragment component.
1670 def fragment=(new_fragment)
1671 if new_fragment && !new_fragment.respond_to?(:to_str)
1672 raise TypeError, "Can't convert #{new_fragment.class} into String."
1673 end
1674 @fragment = new_fragment ? new_fragment.to_str : nil
1675
1676 # Reset dependant values
1677 @normalized_fragment = nil
1678 @uri_string = nil
1679 @hash = nil
1680
1681 # Ensure we haven't created an invalid URI
1682 validate()
1683 end
1684
1685 ##
1686 # Determines if the scheme indicates an IP-based protocol.
1687 #
1688 # @return [TrueClass, FalseClass]
1689 # <code>true</code> if the scheme indicates an IP-based protocol.
1690 # <code>false</code> otherwise.
1691 def ip_based?
1692 if self.scheme
1693 return URI.ip_based_schemes.include?(
1694 self.scheme.strip.downcase)
1695 end
1696 return false
1697 end
1698
1699 ##
1700 # Determines if the URI is relative.
1701 #
1702 # @return [TrueClass, FalseClass]
1703 # <code>true</code> if the URI is relative. <code>false</code>
1704 # otherwise.
1705 def relative?
1706 return self.scheme.nil?
1707 end
1708
1709 ##
1710 # Determines if the URI is absolute.
1711 #
1712 # @return [TrueClass, FalseClass]
1713 # <code>true</code> if the URI is absolute. <code>false</code>
1714 # otherwise.
1715 def absolute?
1716 return !relative?
1717 end
1718
1719 ##
1720 # Joins two URIs together.
1721 #
1722 # @param [String, Addressable::URI, #to_str] The URI to join with.
1723 #
1724 # @return [Addressable::URI] The joined URI.
1725 def join(uri)
1726 if !uri.respond_to?(:to_str)
1727 raise TypeError, "Can't convert #{uri.class} into String."
1728 end
1729 if !uri.kind_of?(URI)
1730 # Otherwise, convert to a String, then parse.
1731 uri = URI.parse(uri.to_str)
1732 end
1733 if uri.to_s.empty?
1734 return self.dup
1735 end
1736
1737 joined_scheme = nil
1738 joined_user = nil
1739 joined_password = nil
1740 joined_host = nil
1741 joined_port = nil
1742 joined_path = nil
1743 joined_query = nil
1744 joined_fragment = nil
1745
1746 # Section 5.2.2 of RFC 3986
1747 if uri.scheme != nil
1748 joined_scheme = uri.scheme
1749 joined_user = uri.user
1750 joined_password = uri.password
1751 joined_host = uri.host
1752 joined_port = uri.port
1753 joined_path = URI.normalize_path(uri.path)
1754 joined_query = uri.query
1755 else
1756 if uri.authority != nil
1757 joined_user = uri.user
1758 joined_password = uri.password
1759 joined_host = uri.host
1760 joined_port = uri.port
1761 joined_path = URI.normalize_path(uri.path)
1762 joined_query = uri.query
1763 else
1764 if uri.path == nil || uri.path.empty?
1765 joined_path = self.path
1766 if uri.query != nil
1767 joined_query = uri.query
1768 else
1769 joined_query = self.query
1770 end
1771 else
1772 if uri.path[0..0] == SLASH
1773 joined_path = URI.normalize_path(uri.path)
1774 else
1775 base_path = self.path.dup
1776 base_path = EMPTY_STR if base_path == nil
1777 base_path = URI.normalize_path(base_path)
1778
1779 # Section 5.2.3 of RFC 3986
1780 #
1781 # Removes the right-most path segment from the base path.
1782 if base_path =~ /\//
1783 base_path.gsub!(/\/[^\/]+$/, SLASH)
1784 else
1785 base_path = EMPTY_STR
1786 end
1787
1788 # If the base path is empty and an authority segment has been
1789 # defined, use a base path of SLASH
1790 if base_path.empty? && self.authority != nil
1791 base_path = SLASH
1792 end
1793
1794 joined_path = URI.normalize_path(base_path + uri.path)
1795 end
1796 joined_query = uri.query
1797 end
1798 joined_user = self.user
1799 joined_password = self.password
1800 joined_host = self.host
1801 joined_port = self.port
1802 end
1803 joined_scheme = self.scheme
1804 end
1805 joined_fragment = uri.fragment
1806
1807 return self.class.new(
1808 :scheme => joined_scheme,
1809 :user => joined_user,
1810 :password => joined_password,
1811 :host => joined_host,
1812 :port => joined_port,
1813 :path => joined_path,
1814 :query => joined_query,
1815 :fragment => joined_fragment
1816 )
1817 end
1818 alias_method :+, :join
1819
1820 ##
1821 # Destructive form of <code>join</code>.
1822 #
1823 # @param [String, Addressable::URI, #to_str] The URI to join with.
1824 #
1825 # @return [Addressable::URI] The joined URI.
1826 #
1827 # @see Addressable::URI#join
1828 def join!(uri)
1829 replace_self(self.join(uri))
1830 end
1831
1832 ##
1833 # Merges a URI with a <code>Hash</code> of components.
1834 # This method has different behavior from <code>join</code>. Any
1835 # components present in the <code>hash</code> parameter will override the
1836 # original components. The path component is not treated specially.
1837 #
1838 # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1839 #
1840 # @return [Addressable::URI] The merged URI.
1841 #
1842 # @see Hash#merge
1843 def merge(hash)
1844 if !hash.respond_to?(:to_hash)
1845 raise TypeError, "Can't convert #{hash.class} into Hash."
1846 end
1847 hash = hash.to_hash
1848
1849 if hash.has_key?(:authority)
1850 if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
1851 raise ArgumentError,
1852 "Cannot specify both an authority and any of the components " +
1853 "within the authority."
1854 end
1855 end
1856 if hash.has_key?(:userinfo)
1857 if (hash.keys & [:user, :password]).any?
1858 raise ArgumentError,
1859 "Cannot specify both a userinfo and either the user or password."
1860 end
1861 end
1862
1863 uri = self.class.new
1864 uri.defer_validation do
1865 # Bunch of crazy logic required because of the composite components
1866 # like userinfo and authority.
1867 uri.scheme =
1868 hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
1869 if hash.has_key?(:authority)
1870 uri.authority =
1871 hash.has_key?(:authority) ? hash[:authority] : self.authority
1872 end
1873 if hash.has_key?(:userinfo)
1874 uri.userinfo =
1875 hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
1876 end
1877 if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
1878 uri.user =
1879 hash.has_key?(:user) ? hash[:user] : self.user
1880 uri.password =
1881 hash.has_key?(:password) ? hash[:password] : self.password
1882 end
1883 if !hash.has_key?(:authority)
1884 uri.host =
1885 hash.has_key?(:host) ? hash[:host] : self.host
1886 uri.port =
1887 hash.has_key?(:port) ? hash[:port] : self.port
1888 end
1889 uri.path =
1890 hash.has_key?(:path) ? hash[:path] : self.path
1891 uri.query =
1892 hash.has_key?(:query) ? hash[:query] : self.query
1893 uri.fragment =
1894 hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
1895 end
1896
1897 return uri
1898 end
1899
1900 ##
1901 # Destructive form of <code>merge</code>.
1902 #
1903 # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1904 #
1905 # @return [Addressable::URI] The merged URI.
1906 #
1907 # @see Addressable::URI#merge
1908 def merge!(uri)
1909 replace_self(self.merge(uri))
1910 end
1911
1912 ##
1913 # Returns the shortest normalized relative form of this URI that uses the
1914 # supplied URI as a base for resolution. Returns an absolute URI if
1915 # necessary. This is effectively the opposite of <code>route_to</code>.
1916 #
1917 # @param [String, Addressable::URI, #to_str] uri The URI to route from.
1918 #
1919 # @return [Addressable::URI]
1920 # The normalized relative URI that is equivalent to the original URI.
1921 def route_from(uri)
1922 uri = URI.parse(uri).normalize
1923 normalized_self = self.normalize
1924 if normalized_self.relative?
1925 raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
1926 end
1927 if uri.relative?
1928 raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
1929 end
1930 if normalized_self == uri
1931 return Addressable::URI.parse("##{normalized_self.fragment}")
1932 end
1933 components = normalized_self.to_hash
1934 if normalized_self.scheme == uri.scheme
1935 components[:scheme] = nil
1936 if normalized_self.authority == uri.authority
1937 components[:user] = nil
1938 components[:password] = nil
1939 components[:host] = nil
1940 components[:port] = nil
1941 if normalized_self.path == uri.path
1942 components[:path] = nil
1943 if normalized_self.query == uri.query
1944 components[:query] = nil
1945 end
1946 else
1947 if uri.path != SLASH and components[:path]
1948 self_splitted_path = split_path(components[:path])
1949 uri_splitted_path = split_path(uri.path)
1950 self_dir = self_splitted_path.shift
1951 uri_dir = uri_splitted_path.shift
1952 while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
1953 self_dir = self_splitted_path.shift
1954 uri_dir = uri_splitted_path.shift
1955 end
1956 components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
1957 end
1958 end
1959 end
1960 end
1961 # Avoid network-path references.
1962 if components[:host] != nil
1963 components[:scheme] = normalized_self.scheme
1964 end
1965 return Addressable::URI.new(
1966 :scheme => components[:scheme],
1967 :user => components[:user],
1968 :password => components[:password],
1969 :host => components[:host],
1970 :port => components[:port],
1971 :path => components[:path],
1972 :query => components[:query],
1973 :fragment => components[:fragment]
1974 )
1975 end
1976
1977 ##
1978 # Returns the shortest normalized relative form of the supplied URI that
1979 # uses this URI as a base for resolution. Returns an absolute URI if
1980 # necessary. This is effectively the opposite of <code>route_from</code>.
1981 #
1982 # @param [String, Addressable::URI, #to_str] uri The URI to route to.
1983 #
1984 # @return [Addressable::URI]
1985 # The normalized relative URI that is equivalent to the supplied URI.
1986 def route_to(uri)
1987 return URI.parse(uri).route_from(self)
1988 end
1989
1990 ##
1991 # Returns a normalized URI object.
1992 #
1993 # NOTE: This method does not attempt to fully conform to specifications.
1994 # It exists largely to correct other people's failures to read the
1995 # specifications, and also to deal with caching issues since several
1996 # different URIs may represent the same resource and should not be
1997 # cached multiple times.
1998 #
1999 # @return [Addressable::URI] The normalized URI.
2000 def normalize
2001 # This is a special exception for the frequently misused feed
2002 # URI scheme.
2003 if normalized_scheme == "feed"
2004 if self.to_s =~ /^feed:\/*http:\/*/
2005 return URI.parse(
2006 self.to_s[/^feed:\/*(http:\/*.*)/, 1]
2007 ).normalize
2008 end
2009 end
2010
2011 return self.class.new(
2012 :scheme => normalized_scheme,
2013 :authority => normalized_authority,
2014 :path => normalized_path,
2015 :query => normalized_query,
2016 :fragment => normalized_fragment
2017 )
2018 end
2019
2020 ##
2021 # Destructively normalizes this URI object.
2022 #
2023 # @return [Addressable::URI] The normalized URI.
2024 #
2025 # @see Addressable::URI#normalize
2026 def normalize!
2027 replace_self(self.normalize)
2028 end
2029
2030 ##
2031 # Creates a URI suitable for display to users. If semantic attacks are
2032 # likely, the application should try to detect these and warn the user.
2033 # See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
2034 # section 7.6 for more information.
2035 #
2036 # @return [Addressable::URI] A URI suitable for display purposes.
2037 def display_uri
2038 display_uri = self.normalize
2039 display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
2040 return display_uri
2041 end
2042
2043 ##
2044 # Returns <code>true</code> if the URI objects are equal. This method
2045 # normalizes both URIs before doing the comparison, and allows comparison
2046 # against <code>Strings</code>.
2047 #
2048 # @param [Object] uri The URI to compare.
2049 #
2050 # @return [TrueClass, FalseClass]
2051 # <code>true</code> if the URIs are equivalent, <code>false</code>
2052 # otherwise.
2053 def ===(uri)
2054 if uri.respond_to?(:normalize)
2055 uri_string = uri.normalize.to_s
2056 else
2057 begin
2058 uri_string = ::Addressable::URI.parse(uri).normalize.to_s
2059 rescue InvalidURIError, TypeError
2060 return false
2061 end
2062 end
2063 return self.normalize.to_s == uri_string
2064 end
2065
2066 ##
2067 # Returns <code>true</code> if the URI objects are equal. This method
2068 # normalizes both URIs before doing the comparison.
2069 #
2070 # @param [Object] uri The URI to compare.
2071 #
2072 # @return [TrueClass, FalseClass]
2073 # <code>true</code> if the URIs are equivalent, <code>false</code>
2074 # otherwise.
2075 def ==(uri)
2076 return false unless uri.kind_of?(URI)
2077 return self.normalize.to_s == uri.normalize.to_s
2078 end
2079
2080 ##
2081 # Returns <code>true</code> if the URI objects are equal. This method
2082 # does NOT normalize either URI before doing the comparison.
2083 #
2084 # @param [Object] uri The URI to compare.
2085 #
2086 # @return [TrueClass, FalseClass]
2087 # <code>true</code> if the URIs are equivalent, <code>false</code>
2088 # otherwise.
2089 def eql?(uri)
2090 return false unless uri.kind_of?(URI)
2091 return self.to_s == uri.to_s
2092 end
2093
2094 ##
2095 # A hash value that will make a URI equivalent to its normalized
2096 # form.
2097 #
2098 # @return [Integer] A hash of the URI.
2099 def hash
2100 return @hash ||= (self.to_s.hash * -1)
2101 end
2102
2103 ##
2104 # Clones the URI object.
2105 #
2106 # @return [Addressable::URI] The cloned URI.
2107 def dup
2108 duplicated_uri = self.class.new(
2109 :scheme => self.scheme ? self.scheme.dup : nil,
2110 :user => self.user ? self.user.dup : nil,
2111 :password => self.password ? self.password.dup : nil,
2112 :host => self.host ? self.host.dup : nil,
2113 :port => self.port,
2114 :path => self.path ? self.path.dup : nil,
2115 :query => self.query ? self.query.dup : nil,
2116 :fragment => self.fragment ? self.fragment.dup : nil
2117 )
2118 return duplicated_uri
2119 end
2120
2121 ##
2122 # Omits components from a URI.
2123 #
2124 # @param [Symbol] *components The components to be omitted.
2125 #
2126 # @return [Addressable::URI] The URI with components omitted.
2127 #
2128 # @example
2129 # uri = Addressable::URI.parse("http://example.com/path?query")
2130 # #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
2131 # uri.omit(:scheme, :authority)
2132 # #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
2133 def omit(*components)
2134 invalid_components = components - [
2135 :scheme, :user, :password, :userinfo, :host, :port, :authority,
2136 :path, :query, :fragment
2137 ]
2138 unless invalid_components.empty?
2139 raise ArgumentError,
2140 "Invalid component names: #{invalid_components.inspect}."
2141 end
2142 duplicated_uri = self.dup
2143 duplicated_uri.defer_validation do
2144 components.each do |component|
2145 duplicated_uri.send((component.to_s + "=").to_sym, nil)
2146 end
2147 duplicated_uri.user = duplicated_uri.normalized_user
2148 end
2149 duplicated_uri
2150 end
2151
2152 ##
2153 # Destructive form of omit.
2154 #
2155 # @param [Symbol] *components The components to be omitted.
2156 #
2157 # @return [Addressable::URI] The URI with components omitted.
2158 #
2159 # @see Addressable::URI#omit
2160 def omit!(*components)
2161 replace_self(self.omit(*components))
2162 end
2163
2164 ##
2165 # Determines if the URI is an empty string.
2166 #
2167 # @return [TrueClass, FalseClass]
2168 # Returns <code>true</code> if empty, <code>false</code> otherwise.
2169 def empty?
2170 return self.to_s.empty?
2171 end
2172
2173 ##
2174 # Converts the URI to a <code>String</code>.
2175 #
2176 # @return [String] The URI's <code>String</code> representation.
2177 def to_s
2178 if self.scheme == nil && self.path != nil && !self.path.empty? &&
2179 self.path =~ NORMPATH
2180 raise InvalidURIError,
2181 "Cannot assemble URI string with ambiguous path: '#{self.path}'"
2182 end
2183 @uri_string ||= (begin
2184 uri_string = ""
2185 uri_string << "#{self.scheme}:" if self.scheme != nil
2186 uri_string << "//#{self.authority}" if self.authority != nil
2187 uri_string << self.path.to_s
2188 uri_string << "?#{self.query}" if self.query != nil
2189 uri_string << "##{self.fragment}" if self.fragment != nil
2190 if uri_string.respond_to?(:force_encoding)
2191 uri_string.force_encoding(Encoding::UTF_8)
2192 end
2193 uri_string
2194 end)
2195 end
2196
2197 ##
2198 # URI's are glorified <code>Strings</code>. Allow implicit conversion.
2199 alias_method :to_str, :to_s
2200
2201 ##
2202 # Returns a Hash of the URI components.
2203 #
2204 # @return [Hash] The URI as a <code>Hash</code> of components.
2205 def to_hash
2206 return {
2207 :scheme => self.scheme,
2208 :user => self.user,
2209 :password => self.password,
2210 :host => self.host,
2211 :port => self.port,
2212 :path => self.path,
2213 :query => self.query,
2214 :fragment => self.fragment
2215 }
2216 end
2217
2218 ##
2219 # Returns a <code>String</code> representation of the URI object's state.
2220 #
2221 # @return [String] The URI object's state, as a <code>String</code>.
2222 def inspect
2223 sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
2224 end
2225
2226 ##
2227 # This method allows you to make several changes to a URI simultaneously,
2228 # which separately would cause validation errors, but in conjunction,
2229 # are valid. The URI will be revalidated as soon as the entire block has
2230 # been executed.
2231 #
2232 # @param [Proc] block
2233 # A set of operations to perform on a given URI.
2234 def defer_validation(&block)
2235 raise LocalJumpError, "No block given." unless block
2236 @validation_deferred = true
2237 block.call()
2238 @validation_deferred = false
2239 validate
2240 return nil
2241 end
2242
2243 private
2244 SELF_REF = '.'
2245 PARENT = '..'
2246
2247 RULE_2A = /\/\.\/|\/\.$/
2248 RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
2249 RULE_2D = /^\.\.?\/?/
2250 RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
2251
2252 ##
2253 # Resolves paths to their simplest form.
2254 #
2255 # @param [String] path The path to normalize.
2256 #
2257 # @return [String] The normalized path.
2258 def self.normalize_path(path)
2259 # Section 5.2.4 of RFC 3986
2260
2261 return nil if path.nil?
2262 normalized_path = path.dup
2263 begin
2264 mod = nil
2265 mod ||= normalized_path.gsub!(RULE_2A, SLASH)
2266
2267 pair = normalized_path.match(RULE_2B_2C)
2268 parent, current = pair[1], pair[2] if pair
2269 if pair && ((parent != SELF_REF && parent != PARENT) ||
2270 (current != SELF_REF && current != PARENT))
2271 mod ||= normalized_path.gsub!(
2272 Regexp.new(
2273 "/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
2274 "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
2275 ), SLASH
2276 )
2277 end
2278
2279 mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
2280 # Non-standard, removes prefixed dotted segments from path.
2281 mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
2282 end until mod.nil?
2283
2284 return normalized_path
2285 end
2286
2287 ##
2288 # Ensures that the URI is valid.
2289 def validate
2290 return if !!@validation_deferred
2291 if self.scheme != nil && self.ip_based? &&
2292 (self.host == nil || self.host.empty?) &&
2293 (self.path == nil || self.path.empty?)
2294 raise InvalidURIError,
2295 "Absolute URI missing hierarchical segment: '#{self.to_s}'"
2296 end
2297 if self.host == nil
2298 if self.port != nil ||
2299 self.user != nil ||
2300 self.password != nil
2301 raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
2302 end
2303 end
2304 if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
2305 self.authority != nil
2306 raise InvalidURIError,
2307 "Cannot have a relative path with an authority set: '#{self.to_s}'"
2308 end
2309 return nil
2310 end
2311
2312 ##
2313 # Replaces the internal state of self with the specified URI's state.
2314 # Used in destructive operations to avoid massive code repetition.
2315 #
2316 # @param [Addressable::URI] uri The URI to replace <code>self</code> with.
2317 #
2318 # @return [Addressable::URI] <code>self</code>.
2319 def replace_self(uri)
2320 # Reset dependant values
2321 instance_variables.each do |var|
2322 instance_variable_set(var, nil)
2323 end
2324
2325 @scheme = uri.scheme
2326 @user = uri.user
2327 @password = uri.password
2328 @host = uri.host
2329 @port = uri.port
2330 @path = uri.path
2331 @query = uri.query
2332 @fragment = uri.fragment
2333 return self
2334 end
2335
2336 ##
2337 # Splits path string with "/"(slash).
2338 # It is considered that there is empty string after last slash when
2339 # path ends with slash.
2340 #
2341 # @param [String] path The path to split.
2342 #
2343 # @return [Array<String>] An array of parts of path.
2344 def split_path(path)
2345 splitted = path.split(SLASH)
2346 splitted << EMPTY_STR if path.end_with? SLASH
2347 splitted
2348 end
2349 end
2350 end
+0
-30
vendor/addressable/version.rb less more
0 # encoding:utf-8
1 #--
2 # Copyright (C) 2006-2013 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 = 6
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
-22
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 def self.json_create(object)
7 Complex(object['r'], object['i'])
8 end
9
10 def as_json(*)
11 {
12 JSON.create_id => self.class.name,
13 'r' => real,
14 'i' => imag,
15 }
16 end
17
18 def to_json(*)
19 as_json.to_json
20 end
21 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
-22
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 def self.json_create(object)
7 Rational(object['n'], object['d'])
8 end
9
10 def as_json(*)
11 {
12 JSON.create_id => self.class.name,
13 'n' => numerator,
14 'd' => denominator,
15 }
16 end
17
18 def to_json(*)
19 as_json.to_json
20 end
21 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 instance_methods.include?(: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 true.
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 true.
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 limit and opts.update(:max_nesting => 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 # Shortuct 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 # Ouputs _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.1'
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 "dl/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 DL::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.0"
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.2"
2 end
+0
-314
vendor/power_assert.rb less more
0 # power_assert.rb
1 #
2 # Copyright (C) 2014 Kazuki Tsujimoto, All rights reserved.
3
4 begin
5 TracePoint.new(:return, :c_return) {}
6 rescue
7 raise LoadError, 'Fully compatible TracePoint API required'
8 end
9
10 require 'power_assert/version'
11 require 'power_assert/enable_tracepoint_events'
12 require 'ripper'
13
14 module PowerAssert
15 class << self
16 def configuration
17 @configuration ||= Configuration[false]
18 end
19
20 def configure
21 yield configuration
22 end
23
24 def start(assertion_proc_or_source, assertion_method: nil, source_binding: TOPLEVEL_BINDING)
25 if respond_to?(:clear_global_method_cache, true)
26 clear_global_method_cache
27 end
28 yield Context.new(assertion_proc_or_source, assertion_method, source_binding)
29 end
30
31 private
32
33 if defined?(RubyVM) and respond_to?(:using, true)
34 def clear_global_method_cache
35 class << Object.new
36 using Empty
37 end
38 end
39 end
40 end
41
42 Configuration = Struct.new(:lazy_inspection)
43 private_constant :Configuration
44
45 module Empty
46 end
47 private_constant :Empty
48
49 class InspectedValue
50 def initialize(value)
51 @value = value
52 end
53
54 def inspect
55 @value
56 end
57 end
58 private_constant :InspectedValue
59
60 class SafeInspectable
61 def initialize(value)
62 @value = value
63 end
64
65 def inspect
66 inspected = @value.inspect
67 if Encoding.compatible?(Encoding.default_external, inspected)
68 inspected
69 else
70 begin
71 "#{inspected.encode(Encoding.default_external)}(#{inspected.encoding})"
72 rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
73 inspected.force_encoding(Encoding.default_external)
74 end
75 end
76 rescue => e
77 "InspectionFailure: #{e.class}: #{e.message.each_line.first}"
78 end
79 end
80 private_constant :SafeInspectable
81
82 class Context
83 Value = Struct.new(:name, :value, :column)
84 Ident = Struct.new(:type, :name, :column)
85
86 TARGET_CALLER_DIFF = {return: 5, c_return: 4}
87 TARGET_INDEX_OFFSET = {bmethod: 3, method: 2}
88
89 attr_reader :message_proc
90
91 def initialize(assertion_proc_or_source, assertion_method, source_binding)
92 if assertion_proc_or_source.kind_of?(Proc)
93 @assertion_proc = assertion_proc_or_source
94 @line = nil
95 else
96 @assertion_proc = source_binding.eval "Proc.new {#{assertion_proc_or_source}}"
97 @line = assertion_proc_or_source
98 end
99 path = nil
100 lineno = nil
101 methods = nil
102 refs = nil
103 method_ids = nil
104 return_values = []
105 @base_caller_length = -1
106 @assertion_method_name = assertion_method.to_s
107 @message_proc = -> {
108 return nil if @base_caller_length < 0
109 @message ||= build_assertion_message(@line || '', methods || [], return_values, refs || [], @assertion_proc.binding).freeze
110 }
111 @proc_local_variables = @assertion_proc.binding.eval('local_variables').map(&:to_s)
112 target_thread = Thread.current
113 @trace = TracePoint.new(:return, :c_return) do |tp|
114 next if method_ids and ! method_ids.include?(tp.method_id)
115 locs = tp.binding.eval('::Kernel.caller_locations')
116 current_diff = locs.length - @base_caller_length
117 target_diff = TARGET_CALLER_DIFF[tp.event]
118 is_target_bmethod = current_diff < target_diff
119 if (is_target_bmethod or current_diff == target_diff) and Thread.current == target_thread
120 idx = target_diff - TARGET_INDEX_OFFSET[is_target_bmethod ? :bmethod : :method]
121 unless path
122 path = locs[idx].path
123 lineno = locs[idx].lineno
124 @line ||= open(path).each_line.drop(lineno - 1).first
125 idents = extract_idents(Ripper.sexp(@line))
126 methods, refs = idents.partition {|i| i.type == :method }
127 method_ids = methods.map(&:name).map(&:to_sym).uniq
128 end
129 if path == locs[idx].path and lineno == locs[idx].lineno
130 val = PowerAssert.configuration.lazy_inspection ?
131 tp.return_value :
132 InspectedValue.new(SafeInspectable.new(tp.return_value).inspect)
133 return_values << Value[tp.method_id.to_s, val, nil]
134 end
135 end
136 end
137 end
138
139 def yield
140 do_yield(&@assertion_proc)
141 end
142
143 private
144
145 def do_yield
146 @trace.enable do
147 @base_caller_length = caller_locations.length
148 yield
149 end
150 end
151
152 def build_assertion_message(line, methods, return_values, refs, proc_binding)
153 set_column(methods, return_values)
154 ref_values = refs.map {|i| Value[i.name, proc_binding.eval(i.name), i.column] }
155 vals = (return_values + ref_values).find_all(&:column).sort_by(&:column).reverse
156 if vals.empty?
157 return line
158 end
159 fmt = (0..vals[0].column).map {|i| vals.find {|v| v.column == i } ? "%<#{i}>s" : ' ' }.join
160 ret = []
161 ret << line.chomp
162 ret << sprintf(fmt, vals.each_with_object({}) {|v, h| h[v.column.to_s.to_sym] = '|' }).chomp
163 vals.each do |i|
164 inspected_vals = vals.each_with_object({}) do |j, h|
165 h[j.column.to_s.to_sym] = [SafeInspectable.new(i.value).inspect, '|', ' '][i.column <=> j.column]
166 end
167 ret << encoding_safe_rstrip(sprintf(fmt, inspected_vals))
168 end
169 ret.join("\n")
170 end
171
172 def set_column(methods, return_values)
173 methods = methods.dup
174 return_values.each do |val|
175 idx = methods.index {|method| method.name == val.name }
176 if idx
177 val.column = methods.delete_at(idx).column
178 end
179 end
180 end
181
182 def encoding_safe_rstrip(str)
183 str.rstrip
184 rescue ArgumentError, Encoding::CompatibilityError
185 enc = str.encoding
186 if enc.ascii_compatible?
187 str.b.rstrip.force_encoding(enc)
188 else
189 str
190 end
191 end
192
193 def extract_idents(sexp)
194 tag, * = sexp
195 case tag
196 when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal
197 extract_idents(sexp[1])
198 when :assign, :massign
199 extract_idents(sexp[2])
200 when :assoclist_from_args, :bare_assoc_hash, :dyna_symbol, :paren, :string_embexpr,
201 :regexp_literal, :xstring_literal
202 sexp[1].flat_map {|s| extract_idents(s) }
203 when :assoc_new, :command, :dot2, :dot3, :string_content
204 sexp[1..-1].flat_map {|s| extract_idents(s) }
205 when :unary
206 handle_columnless_ident([], sexp[1], extract_idents(sexp[2]))
207 when :binary
208 handle_columnless_ident(extract_idents(sexp[1]), sexp[2], extract_idents(sexp[3]))
209 when :call
210 if sexp[3] == :call
211 handle_columnless_ident(extract_idents(sexp[1]), :call, [])
212 else
213 [sexp[1], sexp[3]].flat_map {|s| extract_idents(s) }
214 end
215 when :array
216 sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : []
217 when :command_call
218 [sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) }
219 when :aref
220 handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2]))
221 when :method_add_arg
222 idents = extract_idents(sexp[1])
223 if idents.empty?
224 # idents may be empty(e.g. ->{}.())
225 extract_idents(sexp[2])
226 else
227 idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]]
228 end
229 when :args_add_block
230 _, (tag, ss0, *ss1), _ = sexp
231 if tag == :args_add_star
232 (ss0 + ss1).flat_map {|s| extract_idents(s) }
233 else
234 sexp[1].flat_map {|s| extract_idents(s) }
235 end
236 when :vcall
237 _, (tag, name, (_, column)) = sexp
238 if tag == :@ident
239 [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]]
240 else
241 []
242 end
243 when :program
244 _, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp
245 if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and
246 (tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block)
247 ss.flat_map {|s| extract_idents(s) }
248 else
249 _, (s, *) = sexp
250 extract_idents(s)
251 end
252 when :var_ref
253 _, (tag, ref_name, (_, column)) = sexp
254 case tag
255 when :@kw
256 if ref_name == 'self'
257 [Ident[:ref, 'self', column]]
258 else
259 []
260 end
261 when :@const, :@cvar, :@ivar, :@gvar
262 [Ident[:ref, ref_name, column]]
263 else
264 []
265 end
266 when :@ident, :@const
267 _, method_name, (_, column) = sexp
268 [Ident[:method, method_name, column]]
269 else
270 []
271 end
272 end
273
274 def str_indices(str, re, offset, limit)
275 idx = str.index(re, offset)
276 if idx and idx <= limit
277 [idx, *str_indices(str, re, idx + 1, limit)]
278 else
279 []
280 end
281 end
282
283 MID2SRCTXT = {
284 :[] => '[',
285 :+@ => '+',
286 :-@ => '-',
287 :call => '('
288 }
289
290 def handle_columnless_ident(left_idents, mid, right_idents)
291 left_max = left_idents.max_by(&:column)
292 right_min = right_idents.min_by(&:column)
293 bg = left_max ? left_max.column + left_max.name.length : 0
294 ed = right_min ? right_min.column - 1 : @line.length - 1
295 mname = mid.to_s
296 srctxt = MID2SRCTXT[mid] || mname
297 re = /
298 #{'\b' if /\A\w/ =~ srctxt}
299 #{Regexp.escape(srctxt)}
300 #{'\b' if /\w\z/ =~ srctxt}
301 /x
302 indices = str_indices(@line, re, bg, ed)
303 if left_idents.empty? and right_idents.empty?
304 left_idents + right_idents
305 elsif left_idents.empty?
306 left_idents + right_idents + [Ident[:method, mname, indices.last]]
307 else
308 left_idents + right_idents + [Ident[:method, mname, indices.first]]
309 end
310 end
311 end
312 private_constant :Context
313 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 Fialure 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
-476
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 collector.patterns.concat(auto_runner.pattern) if auto_runner.pattern
90 collector.excludes.concat(auto_runner.exclude) if auto_runner.exclude
91 collector.base = auto_runner.base
92 collector.filter = auto_runner.filters
93 collector.collect(*auto_runner.to_run)
94 end
95
96 # JUST TEST!
97 # register_collector(:xml) do |auto_runner|
98 # require 'test/unit/collector/xml'
99 # collector = Collector::XML.new
100 # collector.filter = auto_runner.filters
101 # collector.collect(auto_runner.to_run[0])
102 # end
103
104 # deprecated
105 register_collector(:object_space) do |auto_runner|
106 require 'test/unit/collector/objectspace'
107 c = Collector::ObjectSpace.new
108 c.filter = auto_runner.filters
109 c.collect($0.sub(/\.rb\Z/, ''))
110 end
111
112 # deprecated
113 register_collector(:dir) do |auto_runner|
114 require 'test/unit/collector/dir'
115 c = Collector::Dir.new
116 c.filter = auto_runner.filters
117 c.pattern.concat(auto_runner.pattern) if auto_runner.pattern
118 c.exclude.concat(auto_runner.exclude) if auto_runner.exclude
119 c.base = auto_runner.base
120 $:.push(auto_runner.base) if auto_runner.base
121 c.collect(*(auto_runner.to_run.empty? ? ['.'] : auto_runner.to_run))
122 end
123
124 attr_reader :suite, :runner_options
125 attr_accessor :filters, :to_run, :pattern, :exclude, :base, :workdir
126 attr_accessor :color_scheme, :listeners
127 attr_writer :runner, :collector
128
129 def initialize(standalone)
130 @standalone = standalone
131 @runner = default_runner
132 @collector = default_collector
133 @filters = []
134 @to_run = []
135 @color_scheme = ColorScheme.default
136 @runner_options = {}
137 @default_arguments = []
138 @workdir = nil
139 @listeners = []
140 config_file = "test-unit.yml"
141 if File.exist?(config_file)
142 load_config(config_file)
143 else
144 load_global_config
145 end
146 yield(self) if block_given?
147 end
148
149 def prepare
150 PREPARE_HOOKS.each do |handler|
151 handler.call(self)
152 end
153 end
154
155 def process_args(args=ARGV)
156 begin
157 args.unshift(*@default_arguments)
158 options.order!(args) {|arg| @to_run << arg}
159 rescue OptionParser::ParseError => e
160 puts e
161 puts options
162 exit(false)
163 end
164 not @to_run.empty?
165 end
166
167 def options
168 @options ||= OptionParser.new do |o|
169 o.banner = "Test::Unit automatic runner."
170 o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
171
172 o.on('-r', '--runner=RUNNER', RUNNERS,
173 "Use the given RUNNER.",
174 "(" + keyword_display(RUNNERS) + ")") do |r|
175 @runner = r
176 end
177
178 o.on('--collector=COLLECTOR', COLLECTORS,
179 "Use the given COLLECTOR.",
180 "(" + keyword_display(COLLECTORS) + ")") do |collector|
181 @collector = collector
182 end
183
184 if (@standalone)
185 o.on('-b', '--basedir=DIR', "Base directory of test suites.") do |b|
186 @base = b
187 end
188
189 o.on('-w', '--workdir=DIR', "Working directory to run tests.") do |w|
190 @workdir = w
191 end
192
193 o.on('-a', '--add=TORUN', Array,
194 "Add TORUN to the list of things to run;",
195 "can be a file or a directory.") do |a|
196 @to_run.concat(a)
197 end
198
199 @pattern = []
200 o.on('-p', '--pattern=PATTERN', Regexp,
201 "Match files to collect against PATTERN.") do |e|
202 @pattern << e
203 end
204
205 @exclude = []
206 o.on('-x', '--exclude=PATTERN', Regexp,
207 "Ignore files to collect against PATTERN.") do |e|
208 @exclude << e
209 end
210 end
211
212 o.on('-n', '--name=NAME', String,
213 "Runs tests matching NAME.",
214 "Use '/PATTERN/' for NAME to use regular expression.") do |name|
215 name = (%r{\A/(.*)/\Z} =~ name ? Regexp.new($1) : name)
216 @filters << lambda do |test|
217 return true if name === test.method_name
218 test_name_without_class_name = test.name.gsub(/\(.+?\)\z/, "")
219 if test_name_without_class_name != test.method_name
220 return true if name === test_name_without_class_name
221 end
222 false
223 end
224 end
225
226 o.on('--ignore-name=NAME', String,
227 "Ignores tests matching NAME.",
228 "Use '/PATTERN/' for NAME to use regular expression.") do |n|
229 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
230 case n
231 when Regexp
232 @filters << proc {|t| n =~ t.method_name ? false : true}
233 else
234 @filters << proc {|t| n != t.method_name}
235 end
236 end
237
238 o.on('-t', '--testcase=TESTCASE', String,
239 "Runs tests in TestCases matching TESTCASE.",
240 "Use '/PATTERN/' for TESTCASE to use regular expression.") do |n|
241 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
242 @filters << lambda do |test|
243 match_test_case_name(test, n)
244 end
245 end
246
247 o.on('--ignore-testcase=TESTCASE', String,
248 "Ignores tests in TestCases matching TESTCASE.",
249 "Use '/PATTERN/' for TESTCASE to use regular expression.") do |n|
250 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
251 @filters << lambda do |test|
252 not match_test_case_name(test, n)
253 end
254 end
255
256 o.on('--location=LOCATION', String,
257 "Runs tests that defined in LOCATION.",
258 "LOCATION is one of PATH:LINE, PATH or LINE") do |location|
259 if /\A\d+\z/ =~ location
260 path = nil
261 line = location.to_i
262 else
263 path, line, = location.split(/:(\d+)/, 2)
264 line = line.to_i unless line.nil?
265 end
266 @filters << lambda do |test|
267 test.class.test_defined?(:path => path,
268 :line => line,
269 :method_name => test.method_name)
270 end
271 end
272
273 o.on('--attribute=EXPRESSION', String,
274 "Runs tests that matches EXPRESSION.",
275 "EXPRESSION is evaluated as Ruby's expression.",
276 "Test attribute name can be used with no receiver in EXPRESSION.",
277 "EXPRESSION examples:",
278 " !slow",
279 " tag == 'important' and !slow") do |expression|
280 @filters << lambda do |test|
281 matcher = AttributeMatcher.new(test)
282 matcher.match?(expression)
283 end
284 end
285
286 priority_filter = Proc.new do |test|
287 if @filters == [priority_filter]
288 Priority::Checker.new(test).need_to_run?
289 else
290 nil
291 end
292 end
293 o.on("--[no-]priority-mode",
294 "Runs some tests based on their priority.") do |priority_mode|
295 if priority_mode
296 Priority.enable
297 @filters |= [priority_filter]
298 else
299 Priority.disable
300 @filters -= [priority_filter]
301 end
302 end
303
304 o.on("--default-priority=PRIORITY",
305 Priority.available_values,
306 "Uses PRIORITY as default priority",
307 "(#{keyword_display(Priority.available_values)})") do |priority|
308 Priority.default = priority
309 end
310
311 o.on('-I', "--load-path=DIR[#{File::PATH_SEPARATOR}DIR...]",
312 "Appends directory list to $LOAD_PATH.") do |dirs|
313 $LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
314 end
315
316 color_schemes = ColorScheme.all
317 o.on("--color-scheme=SCHEME", color_schemes,
318 "Use SCHEME as color scheme.",
319 "(#{keyword_display(color_schemes)})") do |scheme|
320 @color_scheme = scheme
321 end
322
323 o.on("--config=FILE",
324 "Use YAML fomat FILE content as configuration file.") do |file|
325 load_config(file)
326 end
327
328 o.on("--order=ORDER", TestCase::AVAILABLE_ORDERS,
329 "Run tests in a test case in ORDER order.",
330 "(#{keyword_display(TestCase::AVAILABLE_ORDERS)})") do |order|
331 TestCase.test_order = order
332 end
333
334 assertion_message_class = Test::Unit::Assertions::AssertionMessage
335 o.on("--max-diff-target-string-size=SIZE", Integer,
336 "Shows diff if both expected result string size and " +
337 "actual result string size are " +
338 "less than or equal SIZE in bytes.",
339 "(#{assertion_message_class.max_diff_target_string_size})") do |size|
340 assertion_message_class.max_diff_target_string_size = size
341 end
342
343 ADDITIONAL_OPTIONS.each do |option_builder|
344 option_builder.call(self, o)
345 end
346
347 o.on('--',
348 "Stop processing options so that the",
349 "remaining options will be passed to the",
350 "test."){o.terminate}
351
352 o.on('-h', '--help', 'Display this help.'){puts o; exit}
353
354 o.on_tail
355 o.on_tail('Deprecated options:')
356
357 o.on_tail('--console', 'Console runner (use --runner).') do
358 warn("Deprecated option (--console).")
359 @runner = self.class.runner(:console)
360 end
361
362 if RUNNERS[:fox]
363 o.on_tail('--fox', 'Fox runner (use --runner).') do
364 warn("Deprecated option (--fox).")
365 @runner = self.class.runner(:fox)
366 end
367 end
368
369 o.on_tail
370 end
371 end
372
373 def keyword_display(keywords)
374 keywords = keywords.collect do |keyword, _|
375 keyword.to_s
376 end.uniq.sort
377
378 i = 0
379 keywords.collect do |keyword|
380 if (i > 0 and keyword[0] == keywords[i - 1][0]) or
381 ((i < keywords.size - 1) and (keyword[0] == keywords[i + 1][0]))
382 n = 2
383 else
384 n = 1
385 end
386 i += 1
387 keyword.sub(/^(.{#{n}})([A-Za-z]+)(?=\w*$)/, '\\1[\\2]')
388 end.join(", ")
389 end
390
391 def run
392 self.class.need_auto_run = false
393 suite = @collector[self]
394 return false if suite.nil?
395 return true if suite.empty?
396 runner = @runner[self]
397 return false if runner.nil?
398 @runner_options[:color_scheme] ||= @color_scheme
399 @runner_options[:listeners] ||= []
400 @runner_options[:listeners].concat(@listeners)
401 change_work_directory do
402 runner.run(suite, @runner_options).passed?
403 end
404 end
405
406 def load_config(file)
407 require 'yaml'
408 config = YAML.load(File.read(file))
409 runner_name = config["runner"]
410 @runner = self.class.runner(runner_name) || @runner
411 @collector = self.class.collector(config["collector"]) || @collector
412 (config["color_schemes"] || {}).each do |name, options|
413 ColorScheme[name] = options
414 end
415 runner_options = {}
416 (config["#{runner_name}_options"] || {}).each do |key, value|
417 key = key.to_sym
418 value = ColorScheme[value] if key == :color_scheme
419 if key == :arguments
420 @default_arguments.concat(value.split)
421 else
422 runner_options[key.to_sym] = value
423 end
424 end
425 @runner_options = @runner_options.merge(runner_options)
426 end
427
428 private
429 def default_runner
430 runner = self.class.default_runner
431 if ENV["EMACS"] == "t"
432 runner ||= self.class.runner(:emacs)
433 else
434 runner ||= self.class.runner(:console)
435 end
436 runner
437 end
438
439 def default_collector
440 self.class.collector(@standalone ? :load : :descendant)
441 end
442
443 def global_config_file
444 File.expand_path("~/.test-unit.yml")
445 rescue ArgumentError
446 nil
447 end
448
449 def load_global_config
450 file = global_config_file
451 load_config(file) if file and File.exist?(file)
452 end
453
454 def change_work_directory(&block)
455 if @workdir
456 Dir.chdir(@workdir, &block)
457 else
458 yield
459 end
460 end
461
462 def match_test_case_name(test, pattern)
463 test.class.ancestors.each do |test_class|
464 break if test_class == TestCase
465 return true if pattern === test_class.name
466 end
467 false
468 end
469 end
470 end
471 end
472
473 require 'test/unit/runner/console'
474 require 'test/unit/runner/emacs'
475 require 'test/unit/runner/xml'
+0
-57
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.ascii_only?
44 if /\b(?:en)?coding[:=]\s*([a-z\d_-]+)/i =~ first_line
45 begin
46 Encoding.find($1)
47 rescue ArgumentError
48 nil
49 end
50 else
51 nil
52 end
53 end
54 end
55 end
56 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
-159
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 "failure" => Color.new("red", :background => true) +
22 Color.new("white", :bold => true),
23 "pending" => Color.new("magenta", :background => true) +
24 Color.new("white", :bold => true),
25 "omission" => Color.new("blue", :background => true) +
26 Color.new("white", :bold => true),
27 "notification" => Color.new("cyan", :background => true) +
28 Color.new("white", :bold => true),
29 "error" => Color.new("black", :background => true) +
30 Color.new("yellow", :bold => true),
31 "case" => Color.new("blue", :background => true) +
32 Color.new("white", :bold => true),
33 "suite" => Color.new("green", :background => true) +
34 Color.new("white", :bold => true),
35 "diff-inserted-tag" => Color.new("red", :background => true) +
36 Color.new("black", :bold => true),
37 "diff-deleted-tag" => Color.new("green", :background => true) +
38 Color.new("black", :bold => true),
39 "diff-difference-tag" => Color.new("cyan", :background => true) +
40 Color.new("white", :bold => true),
41 "diff-inserted" => Color.new("red", :background => true) +
42 Color.new("white", :bold => true),
43 "diff-deleted" => Color.new("green", :background => true) +
44 Color.new("white", :bold => true))
45 end
46
47 @@default_for_256_colors = nil
48 def default_for_256_colors
49 @@default_for_256_colors ||=
50 new("pass" => Color.new("030", :background => true) +
51 Color.new("555", :bold => true),
52 "failure" => Color.new("300", :background => true) +
53 Color.new("555", :bold => true),
54 "pending" => Color.new("303", :background => true) +
55 Color.new("555", :bold => true),
56 "omission" => Color.new("001", :background => true) +
57 Color.new("555", :bold => true),
58 "notification" => Color.new("011", :background => true) +
59 Color.new("555", :bold => true),
60 "error" => Color.new("000", :background => true) +
61 Color.new("550", :bold => true),
62 "case" => Color.new("220", :background => true) +
63 Color.new("555", :bold => true),
64 "suite" => Color.new("110", :background => true) +
65 Color.new("555", :bold => true),
66 "diff-inserted-tag" => Color.new("500", :background => true) +
67 Color.new("000", :bold => true),
68 "diff-deleted-tag" => Color.new("050", :background => true) +
69 Color.new("000", :bold => true),
70 "diff-difference-tag" => Color.new("005", :background => true) +
71 Color.new("555", :bold => true),
72 "diff-inserted" => Color.new("300", :background => true) +
73 Color.new("555", :bold => true),
74 "diff-deleted" => Color.new("030", :background => true) +
75 Color.new("555", :bold => true))
76 end
77
78 @@schemes = {}
79 def all
80 @@schemes.merge("default" => default)
81 end
82
83 def [](id)
84 @@schemes[id.to_s]
85 end
86
87 def []=(id, scheme_or_spec)
88 if scheme_or_spec.is_a?(self)
89 scheme = scheme_or_spec
90 else
91 scheme = new(scheme_or_spec)
92 end
93 @@schemes[id.to_s] = scheme
94 end
95
96 def available_colors
97 case ENV["COLORTERM"]
98 when "gnome-terminal"
99 256
100 else
101 case ENV["TERM"]
102 when /-256color\z/
103 256
104 else
105 8
106 end
107 end
108 end
109 end
110
111 def initialize(scheme_spec)
112 @scheme = {}
113 scheme_spec.each do |key, color_spec|
114 self[key] = color_spec
115 end
116 end
117
118 def [](name)
119 @scheme[name.to_s]
120 end
121
122 def []=(name, color_spec)
123 @scheme[name.to_s] = make_color(color_spec)
124 end
125
126 def each(&block)
127 @scheme.each(&block)
128 end
129
130 def to_hash
131 hash = {}
132 @scheme.each do |key, color|
133 hash[key] = color
134 end
135 hash
136 end
137
138 private
139 def make_color(color_spec)
140 if color_spec.is_a?(Color) or color_spec.is_a?(MixColor)
141 color_spec
142 else
143 color_name = nil
144 normalized_color_spec = {}
145 color_spec.each do |key, value|
146 key = key.to_sym
147 if key == :name
148 color_name = value
149 else
150 normalized_color_spec[key] = value
151 end
152 end
153 Color.new(color_name, normalized_color_spec)
154 end
155 end
156 end
157 end
158 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
-154
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 = [NoMemoryError, SignalException, Interrupt,
81 SystemExit]
82 PASS_THROUGH_EXCEPTION_NAMES = []
83 private
84 def handle_all_exception(exception)
85 return false if pass_through_exception?(exception)
86
87 problem_occurred
88 add_error(exception)
89 true
90 end
91
92 def pass_through_exception?(exception)
93 case exception
94 when *NOT_PASS_THROUGH_EXCEPTIONS
95 return false
96 end
97 case exception.class.name
98 when *NOT_PASS_THROUGH_EXCEPTION_NAMES
99 return false
100 end
101
102 case exception
103 when *PASS_THROUGH_EXCEPTIONS
104 return true
105 end
106 case exception.class.name
107 when *PASS_THROUGH_EXCEPTION_NAMES
108 return true
109 end
110
111 false
112 end
113
114 def add_error(exception)
115 error = Error.new(name, exception, :method_name => @method_name)
116 current_result.add_error(error)
117 end
118 end
119
120 module TestResultErrorSupport
121 attr_reader :errors
122
123 # Records a Test::Unit::Error.
124 def add_error(error)
125 @errors << error
126 notify_fault(error)
127 notify_changed
128 end
129
130 # Returns the number of errors this TestResult has
131 # recorded.
132 def error_count
133 @errors.size
134 end
135
136 def error_occurred?
137 not @errors.empty?
138 end
139
140 private
141 def initialize_containers
142 super
143 @errors = []
144 @summary_generators << :error_summary
145 @problem_checkers << :error_occurred?
146 end
147
148 def error_summary
149 "#{error_count} errors"
150 end
151 end
152 end
153 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
-182
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 = [".test-result",
125 @test.class.name || "AnonymousTestCase",
126 escaped_method_name]
127 parent_directories = [File.dirname($0), Dir.pwd]
128 if Process.respond_to?(:uid)
129 parent_directories << File.join(Dir.tmpdir, Process.uid.to_s)
130 end
131 parent_directories.each do |parent_directory|
132 dir = File.expand_path(File.join(parent_directory, *components))
133 begin
134 FileUtils.mkdir_p(dir)
135 return dir
136 rescue Errno::EACCES
137 end
138 end
139
140 raise Errno::EACCES, parent_directories.join(", ")
141 end
142
143 def passed_file
144 File.join(result_dir, "passed")
145 end
146
147 def escaped_method_name
148 @test.method_name.to_s.gsub(/[!?=]$/) do |matched|
149 case matched
150 when "!"
151 ".destructive"
152 when "?"
153 ".predicate"
154 when "="
155 ".equal"
156 end
157 end
158 end
159 end
160
161 module ClassMethods
162 def priority(name, *tests)
163 unless Checker.have_priority?(name)
164 raise ArgumentError, "unknown priority: #{name}"
165 end
166 attribute(:priority, name, {:keep => true}, *tests)
167 end
168 end
169
170 def priority_setup
171 return unless Priority.enabled?
172 Checker.new(self).setup
173 end
174
175 def priority_teardown
176 return unless Priority.enabled?
177 Checker.new(self).teardown
178 end
179 end
180 end
181 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
-81
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 method_names = methods.collect(&:to_s)
47 test_names = method_names.find_all do |method_name|
48 method_name =~ /^test./ or
49 @test_case.find_attribute(method_name, :test)
50 end
51 __send__("sort_test_names_in_#{@test_case.test_order}_order", test_names)
52 end
53
54 def sort_test_names_in_alphabetic_order(test_names)
55 test_names.sort
56 end
57
58 def sort_test_names_in_random_order(test_names)
59 test_names.sort_by {rand(test_names.size)}
60 end
61
62 def sort_test_names_in_defined_order(test_names)
63 added_method_names = @test_case.added_method_names
64 test_names.sort do |test1, test2|
65 test1_defined_order = added_method_names.index(test1)
66 test2_defined_order = added_method_names.index(test2)
67 if test1_defined_order and test2_defined_order
68 test1_defined_order <=> test2_defined_order
69 elsif test1_defined_order
70 1
71 elsif test2_defined_order
72 -1
73 else
74 test1 <=> test2
75 end
76 end
77 end
78 end
79 end
80 end
+0
-764
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 diffrence 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 evaludated 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 # Checkes whether a test that is mathched 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 = method_locations
363 if query_path
364 available_locations = available_locations.find_all do |location|
365 location[:path].end_with?(query_path)
366 end
367 end
368 if query_line
369 available_location = available_locations.reverse.find do |location|
370 query_line >= location[:line]
371 end
372 return false if available_location.nil?
373 available_locations = [available_location]
374 end
375 if query_method_name
376 available_location = available_locations.find do |location|
377 query_method_name == location[:method_name]
378 end
379 return false if available_location.nil?
380 available_locations = [available_location]
381 end
382
383 not available_locations.empty?
384 end
385
386 private
387 # @private
388 @@method_locations = {}
389 # @private
390 def method_locations
391 @@method_locations[self] ||= []
392 end
393 end
394
395 attr_reader :method_name
396
397 # Creates a new instance of the fixture for running the
398 # test represented by test_method_name.
399 def initialize(test_method_name)
400 @method_name = test_method_name
401 @internal_data = InternalData.new
402 end
403
404 # Assigns test data to the test. It is used in internal.
405 def assign_test_data(label, data) # :nodoc:
406 @internal_data.assign_test_data(label, data)
407 end
408
409 # Returns the test is valid test. It is used in internal.
410 def valid? # :nodoc:
411 return false unless respond_to?(@method_name)
412 test_method = method(@method_name)
413 if @internal_data.have_test_data?
414 return false unless test_method.arity == 1
415 else
416 return false unless test_method.arity <= 0
417 end
418 owner = Util::MethodOwnerFinder.find(self, @method_name)
419 if owner.class != Module and self.class != owner
420 return false
421 end
422 true
423 end
424
425 # Runs the individual test method represented by this
426 # instance of the fixture, collecting statistics, failures
427 # and errors in result.
428 def run(result)
429 begin
430 @_result = result
431 @internal_data.test_started
432 yield(STARTED, name)
433 yield(STARTED_OBJECT, self)
434 begin
435 run_setup
436 run_test
437 run_cleanup
438 add_pass
439 rescue Exception
440 @internal_data.interrupted
441 raise unless handle_exception($!)
442 ensure
443 begin
444 run_teardown
445 rescue Exception
446 raise unless handle_exception($!)
447 end
448 end
449 @internal_data.test_finished
450 result.add_run
451 yield(FINISHED, name)
452 yield(FINISHED_OBJECT, self)
453 ensure
454 # @_result = nil # For test-spec's after_all :<
455 end
456 end
457
458 # Called before every test method runs. Can be used
459 # to set up fixture information.
460 #
461 # You can add additional setup tasks by the following
462 # code:
463 # class TestMyClass < Test::Unit::TestCase
464 # def setup
465 # ...
466 # end
467 #
468 # setup
469 # def my_setup1
470 # ...
471 # end
472 #
473 # setup do
474 # ... # setup callback1
475 # end
476 #
477 # setup
478 # def my_setup2
479 # ...
480 # end
481 #
482 # setup do
483 # ... # setup callback2
484 # end
485 #
486 # def test_my_class
487 # ...
488 # end
489 # end
490 #
491 # Here is a call order:
492 # * setup
493 # * my_setup1
494 # * setup callback1
495 # * my_setup2
496 # * setup callback2
497 # * test_my_class
498 def setup
499 end
500
501 # Called after every test method runs but the test
502 # method isn't marked as 'passed'. Can be used to
503 # clean up and/or verify tested condition.
504 # e.g. Can be used to verify mock.
505 #
506 # You can add additional cleanup tasks by the following
507 # code:
508 # class TestMyClass < Test::Unit::TestCase
509 # def cleanup
510 # ...
511 # end
512 #
513 # cleanup
514 # def my_cleanup1
515 # ...
516 # end
517 #
518 # cleanup do
519 # ... # cleanup callback1
520 # end
521 #
522 # cleanup
523 # def my_cleanup2
524 # ...
525 # end
526 #
527 # cleanup do
528 # ... # cleanup callback2
529 # end
530 #
531 # def test_my_class
532 # ...
533 # end
534 # end
535 #
536 # Here is a call order:
537 # * test_my_class
538 # * cleanup callback2
539 # * my_cleanup2
540 # * cleanup callback1
541 # * my_cleanup1
542 # * cleanup
543 def cleanup
544 end
545
546 # Called after every test method runs. Can be used to tear
547 # down fixture information.
548 #
549 # You can add additional teardown tasks by the following
550 # code:
551 # class TestMyClass < Test::Unit::TestCase
552 # def teardown
553 # ...
554 # end
555 #
556 # teardown
557 # def my_teardown1
558 # ...
559 # end
560 #
561 # teardown do
562 # ... # teardown callback1
563 # end
564 #
565 # teardown
566 # def my_teardown2
567 # ...
568 # end
569 #
570 # teardown do
571 # ... # teardown callback2
572 # end
573 #
574 # def test_my_class
575 # ...
576 # end
577 # end
578 #
579 # Here is a call order:
580 # * test_my_class
581 # * teardown callback2
582 # * my_teardown2
583 # * teardown callback1
584 # * my_teardown1
585 # * teardown
586 def teardown
587 end
588
589 def default_test
590 flunk("No tests were specified")
591 end
592
593 def size
594 1
595 end
596
597 # Returns a label of test data for the test. If the
598 # test isn't associated with any test data, it returns
599 # +nil+.
600 def data_label
601 @internal_data.test_data_label
602 end
603
604 # Returns a human-readable name for the specific test that
605 # this instance of TestCase represents.
606 def name
607 if @internal_data.have_test_data?
608 "#{@method_name}[#{data_label}](#{self.class.name})"
609 else
610 "#{@method_name}(#{self.class.name})"
611 end
612 end
613
614 # Returns a description for the test. A description
615 # will be associated by Test::Unit::TestCase.test or
616 # Test::Unit::TestCase.description.
617 #
618 # Returns a name for the test for no description test.
619 def description
620 self[:description] || name
621 end
622
623 # Overridden to return #name.
624 def to_s
625 name
626 end
627
628 # It's handy to be able to compare TestCase instances.
629 def ==(other)
630 return false unless other.kind_of?(self.class)
631 return false unless @method_name == other.method_name
632 return false unless data_label == other.data_label
633 self.class == other.class
634 end
635
636 # Returns a Time at the test was started.
637 def start_time
638 @internal_data.start_time
639 end
640
641 # Returns elapsed time for the test was ran.
642 def elapsed_time
643 @internal_data.elapsed_time
644 end
645
646 # Returns whether the test is interrupted.
647 def interrupted?
648 @internal_data.interrupted?
649 end
650
651 # Returns whether this individual test passed or
652 # not. Primarily for use in teardown so that artifacts
653 # can be left behind if the test fails.
654 def passed?
655 @internal_data.passed?
656 end
657
658 # Notify that a problem is occurred in the test. It means that
659 # the test is a failed test. If any failed tests exist in test
660 # suites, the test process exits with failure exit status.
661 #
662 # This is a public API for developers who extend test-unit.
663 #
664 # @return [void]
665 def problem_occurred
666 @internal_data.problem_occurred
667 end
668
669 # Notify that the test is passed. Normally, it is not needed
670 # because #run calls it automatically. If you want to override
671 # #run, it is not a good idea. Please contact test-unit
672 # developers. We will help you without your custom #run. For
673 # example, we may add a new hook in #run.
674 #
675 # This is a public API for developers who extend test-unit.
676 #
677 # @return [void]
678 def add_pass
679 current_result.add_pass
680 end
681
682 private
683 def current_result
684 @_result
685 end
686
687 def run_test
688 redefined_info = self[:redefined]
689 if redefined_info
690 notify("#{self.class}\##{@method_name} was redefined",
691 :backtrace => redefined_info[:backtrace])
692 end
693 if @internal_data.have_test_data?
694 __send__(@method_name, @internal_data.test_data)
695 else
696 __send__(@method_name)
697 end
698 end
699
700 def handle_exception(exception)
701 self.class.exception_handlers.each do |handler|
702 if handler.respond_to?(:call)
703 handled = handler.call(self, exception)
704 else
705 handled = __send__(handler, exception)
706 end
707 return true if handled
708 end
709 false
710 end
711
712 def add_assertion
713 current_result.add_assertion
714 end
715
716 class InternalData
717 attr_reader :start_time, :elapsed_time
718 attr_reader :test_data_label, :test_data
719 def initialize
720 @start_time = nil
721 @elapsed_time = nil
722 @passed = true
723 @interrupted = false
724 @test_data_label = nil
725 @test_data = nil
726 end
727
728 def passed?
729 @passed
730 end
731
732 def interrupted?
733 @interrupted
734 end
735
736 def assign_test_data(label, data)
737 @test_data_label = label
738 @test_data = data
739 end
740
741 def have_test_data?
742 not @test_data_label.nil?
743 end
744
745 def test_started
746 @start_time = Time.now
747 end
748
749 def test_finished
750 @elapsed_time = Time.now - @start_time
751 end
752
753 def problem_occurred
754 @passed = false
755 end
756
757 def interrupted
758 @interrupted = true
759 end
760 end
761 end
762 end
763 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
-172
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 run_shutdown(result)
56 ensure
57 @elapsed_time = Time.now - @start_time
58 yield(FINISHED, name)
59 yield(FINISHED_OBJECT, self)
60 end
61
62 # Adds the test to the suite.
63 def <<(test)
64 @tests << test
65 self
66 end
67
68 def delete(test)
69 @tests.delete(test)
70 end
71
72 def delete_tests(tests)
73 @tests -= tests
74 end
75
76 # Retuns the rolled up number of tests in this suite;
77 # i.e. if the suite contains other suites, it counts the
78 # tests within those suites, not the suites themselves.
79 def size
80 total_size = @n_tests
81 @tests.each { |test| total_size += test.size }
82 total_size
83 end
84
85 def empty?
86 size.zero?
87 end
88
89 # Overridden to return the name given the suite at
90 # creation.
91 def to_s
92 @name
93 end
94
95 # It's handy to be able to compare TestSuite instances.
96 def ==(other)
97 return false unless(other.kind_of?(self.class))
98 return false unless(@name == other.name)
99 @tests == other.tests
100 end
101
102 def passed?
103 @passed
104 end
105
106 private
107 def run_startup(result)
108 return if @test_case.nil? or !@test_case.respond_to?(:startup)
109 begin
110 @test_case.startup
111 rescue Exception
112 raise unless handle_exception($!, result)
113 end
114 end
115
116 def run_test(test, result)
117 finished_is_yielded = false
118 finished_object_is_yielded = false
119 previous_event_name = nil
120 test.run(result) do |event_name, *args|
121 case previous_event_name
122 when Test::Unit::TestCase::STARTED
123 if event_name != Test::Unit::TestCase::STARTED_OBJECT
124 yield(Test::Unit::TestCase::STARTED_OBJECT, test)
125 end
126 when Test::Unit::TestCase::FINISHED
127 if event_name != Test::Unit::TestCase::FINISHED_OBJECT
128 yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
129 end
130 finished_object_is_yielded = true
131 end
132
133 case event_name
134 when Test::Unit::TestCase::STARTED
135 finished_is_yielded = false
136 finished_object_is_yielded = false
137 when Test::Unit::TestCase::FINISHED
138 finished_is_yielded = true
139 end
140
141 previous_event_name = event_name
142 yield(event_name, *args)
143 end
144
145 if finished_is_yielded and not finished_object_is_yielded
146 yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
147 end
148 end
149
150 def run_shutdown(result)
151 return if @test_case.nil? or !@test_case.respond_to?(:shutdown)
152 begin
153 @test_case.shutdown
154 rescue Exception
155 raise unless handle_exception($!, result)
156 end
157 end
158
159 def handle_exception(exception, result)
160 case exception
161 when *ErrorHandler::PASS_THROUGH_EXCEPTIONS
162 false
163 else
164 result.add_error(Error.new(@test_case.name, exception))
165 @passed = false
166 true
167 end
168 end
169 end
170 end
171 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
-630
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 require 'test/unit/color-scheme'
9 require 'test/unit/code-snippet-fetcher'
10 require 'test/unit/fault-location-detector'
11 require 'test/unit/diff'
12 require 'test/unit/ui/testrunner'
13 require 'test/unit/ui/testrunnermediator'
14 require 'test/unit/ui/console/outputlevel'
15
16 module Test
17 module Unit
18 module UI
19 module Console
20
21 # Runs a Test::Unit::TestSuite on the console.
22 class TestRunner < UI::TestRunner
23 include OutputLevel
24
25 # Creates a new TestRunner for running the passed
26 # suite. If quiet_mode is true, the output while
27 # running is limited to progress dots, errors and
28 # failures, and the final result. io specifies
29 # where runner output should go to; defaults to
30 # STDOUT.
31 def initialize(suite, options={})
32 super
33 @output_level = @options[:output_level] || NORMAL
34 @output = @options[:output] || STDOUT
35 @use_color = @options[:use_color]
36 @use_color = guess_color_availability if @use_color.nil?
37 @color_scheme = @options[:color_scheme] || ColorScheme.default
38 @reset_color = Color.new("reset")
39 @progress_row = 0
40 @progress_row_max = @options[:progress_row_max]
41 @progress_row_max ||= guess_progress_row_max
42 @show_detail_immediately = @options[:show_detail_immediately]
43 @show_detail_immediately = true if @show_detail_immediately.nil?
44 @already_outputted = false
45 @indent = 0
46 @top_level = true
47 @current_output_level = NORMAL
48 @faults = []
49 @code_snippet_fetcher = CodeSnippetFetcher.new
50 @test_suites = []
51 end
52
53 private
54 def change_output_level(level)
55 old_output_level = @current_output_level
56 @current_output_level = level
57 yield
58 @current_output_level = old_output_level
59 end
60
61 def setup_mediator
62 super
63 output_setup_end
64 end
65
66 def output_setup_end
67 suite_name = @suite.to_s
68 suite_name = @suite.name if @suite.kind_of?(Module)
69 output("Loaded suite #{suite_name}")
70 end
71
72 def attach_to_mediator
73 @mediator.add_listener(TestResult::FAULT,
74 &method(:add_fault))
75 @mediator.add_listener(TestRunnerMediator::STARTED,
76 &method(:started))
77 @mediator.add_listener(TestRunnerMediator::FINISHED,
78 &method(:finished))
79 @mediator.add_listener(TestCase::STARTED_OBJECT,
80 &method(:test_started))
81 @mediator.add_listener(TestCase::FINISHED_OBJECT,
82 &method(:test_finished))
83 @mediator.add_listener(TestSuite::STARTED_OBJECT,
84 &method(:test_suite_started))
85 @mediator.add_listener(TestSuite::FINISHED_OBJECT,
86 &method(:test_suite_finished))
87 end
88
89 def add_fault(fault)
90 @faults << fault
91 output_progress(fault.single_character_display, fault_color(fault))
92 output_progress_in_detail(fault) if @show_detail_immediately
93 @already_outputted = true if fault.critical?
94 end
95
96 def started(result)
97 @result = result
98 output_started
99 end
100
101 def output_started
102 output("Started")
103 end
104
105 def finished(elapsed_time)
106 nl if output?(NORMAL) and !output?(VERBOSE)
107 output_faults unless @show_detail_immediately
108 nl(PROGRESS_ONLY)
109 change_output_level(IMPORTANT_FAULTS_ONLY) do
110 output_statistics(elapsed_time)
111 end
112 end
113
114 def output_faults
115 categorized_faults = categorize_faults
116 change_output_level(IMPORTANT_FAULTS_ONLY) do
117 output_faults_in_detail(categorized_faults[:need_detail_faults])
118 end
119 output_faults_in_short("Omissions", Omission,
120 categorized_faults[:omissions])
121 output_faults_in_short("Notifications", Notification,
122 categorized_faults[:notifications])
123 end
124
125 def max_digit(max_number)
126 (Math.log10(max_number) + 1).truncate
127 end
128
129 def output_faults_in_detail(faults)
130 return if faults.nil?
131 digit = max_digit(faults.size)
132 faults.each_with_index do |fault, index|
133 nl
134 output_single("%#{digit}d) " % (index + 1))
135 output_fault_in_detail(fault)
136 end
137 end
138
139 def output_faults_in_short(label, fault_class, faults)
140 return if faults.nil?
141 digit = max_digit(faults.size)
142 nl
143 output_single(label, fault_class_color(fault_class))
144 output(":")
145 faults.each_with_index do |fault, index|
146 output_single("%#{digit}d) " % (index + 1))
147 output_fault_in_short(fault)
148 end
149 end
150
151 def categorize_faults
152 faults = {}
153 @faults.each do |fault|
154 category = categorize_fault(fault)
155 faults[category] ||= []
156 faults[category] << fault
157 end
158 faults
159 end
160
161 def categorize_fault(fault)
162 case fault
163 when Omission
164 :omissions
165 when Notification
166 :notifications
167 else
168 :need_detail_faults
169 end
170 end
171
172 def output_fault_in_detail(fault)
173 if fault.is_a?(Failure) and
174 fault.inspected_expected and fault.inspected_actual
175 output_single(fault.label, fault_color(fault))
176 output(":")
177 output(fault.test_name)
178 output_fault_backtrace(fault)
179 output_failure_message(fault)
180 else
181 output_single(fault.label, fault_color(fault))
182 if fault.is_a?(Error)
183 output(": #{fault.test_name}")
184 output_fault_message(fault)
185 else
186 output_fault_message(fault)
187 output(fault.test_name)
188 end
189 output_fault_backtrace(fault)
190 end
191 end
192
193 def output_fault_message(fault)
194 message = fault.message
195 return if message.nil?
196
197 if message.include?("\n")
198 output(":")
199 message.each_line do |line|
200 output(" #{line.chomp}")
201 end
202 else
203 output(": #{message}")
204 end
205 end
206
207 def output_fault_backtrace(fault)
208 snippet_is_shown = false
209 detector = FaultLocationDetector.new(fault, @code_snippet_fetcher)
210 backtrace = fault.location
211 # workaround for test-spec. :<
212 # see also GitHub:#22
213 backtrace ||= []
214 backtrace.each_with_index do |entry, i|
215 output(entry)
216 next if snippet_is_shown
217 next unless detector.target?(entry)
218 file, line_number, = detector.split_backtrace_entry(entry)
219 snippet_is_shown = output_code_snippet(file, line_number,
220 fault_color(fault))
221 end
222 end
223
224 def output_code_snippet(file, line_number, target_line_color=nil)
225 lines = @code_snippet_fetcher.fetch(file, line_number)
226 return false if lines.empty?
227
228 max_n = lines.collect {|n, line, attributes| n}.max
229 digits = (Math.log10(max_n) + 1).truncate
230 lines.each do |n, line, attributes|
231 if attributes[:target_line?]
232 line_color = target_line_color
233 current_line_mark = "=>"
234 else
235 line_color = nil
236 current_line_mark = ""
237 end
238 output(" %2s %*d: %s" % [current_line_mark, digits, n, line],
239 line_color)
240 end
241 true
242 end
243
244 def output_failure_message(failure)
245 if failure.expected.respond_to?(:encoding) and
246 failure.actual.respond_to?(:encoding) and
247 failure.expected.encoding != failure.actual.encoding
248 need_encoding = true
249 else
250 need_encoding = false
251 end
252 output(failure.user_message) if failure.user_message
253 output_single("<")
254 output_single(failure.inspected_expected, color("pass"))
255 output_single(">")
256 if need_encoding
257 output_single("(")
258 output_single(failure.expected.encoding.name, color("pass"))
259 output_single(")")
260 end
261 output(" expected but was")
262 output_single("<")
263 output_single(failure.inspected_actual, color("failure"))
264 output_single(">")
265 if need_encoding
266 output_single("(")
267 output_single(failure.actual.encoding.name, color("failure"))
268 output_single(")")
269 end
270 output("")
271 from, to = prepare_for_diff(failure.expected, failure.actual)
272 if from and to
273 if need_encoding
274 unless from.valid_encoding?
275 from = from.dup.force_encoding("ASCII-8BIT")
276 end
277 unless to.valid_encoding?
278 to = to.dup.force_encoding("ASCII-8BIT")
279 end
280 end
281 from_lines = from.split(/\r?\n/)
282 to_lines = to.split(/\r?\n/)
283 if need_encoding
284 from_lines << ""
285 to_lines << ""
286 from_lines << "Encoding: #{failure.expected.encoding.name}"
287 to_lines << "Encoding: #{failure.actual.encoding.name}"
288 end
289 differ = ColorizedReadableDiffer.new(from_lines, to_lines, self)
290 if differ.need_diff?
291 output("")
292 output("diff:")
293 differ.diff
294 end
295 end
296 end
297
298 def output_fault_in_short(fault)
299 output_single(fault.message, fault_color(fault))
300 output(" [#{fault.test_name}]")
301 output(fault.location.first)
302 end
303
304 def format_fault(fault)
305 fault.long_display
306 end
307
308 def output_statistics(elapsed_time)
309 output("Finished in #{elapsed_time} seconds.")
310 nl
311 output(@result, result_color)
312 output("%g%% passed" % @result.pass_percentage, result_color)
313 unless elapsed_time.zero?
314 nl
315 test_throughput = @result.run_count / elapsed_time
316 assertion_throughput = @result.assertion_count / elapsed_time
317 throughput = [
318 "%.2f tests/s" % test_throughput,
319 "%.2f assertions/s" % assertion_throughput,
320 ]
321 output(throughput.join(", "))
322 end
323 end
324
325 def test_started(test)
326 return unless output?(VERBOSE)
327
328 name = test.name.sub(/\(.+?\)\z/, '')
329 right_space = 8 * 2
330 left_space = @progress_row_max - right_space
331 left_space = left_space - indent.size - name.size
332 tab_stop = "\t" * ([left_space - 1, 0].max / 8)
333 output_single("#{indent}#{name}:#{tab_stop}", nil, VERBOSE)
334 @test_start = Time.now
335 end
336
337 def test_finished(test)
338 unless @already_outputted
339 output_progress(".", color("pass"))
340 end
341 @already_outputted = false
342
343 return unless output?(VERBOSE)
344
345 output(": (%f)" % (Time.now - @test_start), nil, VERBOSE)
346 end
347
348 def suite_name(prefix, suite)
349 name = suite.name
350 if name.nil?
351 "(anonymous)"
352 else
353 name.sub(/\A#{Regexp.escape(prefix)}/, "")
354 end
355 end
356
357 def test_suite_started(suite)
358 last_test_suite = @test_suites.last
359 @test_suites << suite
360 if @top_level
361 @top_level = false
362 return
363 end
364
365 output_single(indent, nil, VERBOSE)
366 if suite.test_case.nil?
367 _color = color("suite")
368 else
369 _color = color("case")
370 end
371 prefix = "#{last_test_suite.name}::"
372 output_single(suite_name(prefix, suite), _color, VERBOSE)
373 output(": ", nil, VERBOSE)
374 @indent += 2
375 end
376
377 def test_suite_finished(suite)
378 @indent -= 2
379 @test_suites.pop
380 end
381
382 def indent
383 if output?(VERBOSE)
384 " " * @indent
385 else
386 ""
387 end
388 end
389
390 def nl(level=nil)
391 output("", nil, level)
392 end
393
394 def output(something, color=nil, level=nil)
395 return unless output?(level)
396 output_single(something, color, level)
397 @output.puts
398 end
399
400 def output_single(something, color=nil, level=nil)
401 return false unless output?(level)
402 if @use_color and color
403 something = "%s%s%s" % [color.escape_sequence,
404 something,
405 @reset_color.escape_sequence]
406 end
407 @output.write(something)
408 @output.flush
409 true
410 end
411
412 def output_progress(mark, color=nil)
413 if output_single(mark, color, PROGRESS_ONLY)
414 return unless @progress_row_max > 0
415 @progress_row += mark.size
416 if @progress_row >= @progress_row_max
417 nl unless @output_level == VERBOSE
418 @progress_row = 0
419 end
420 end
421 end
422
423 def output_progress_in_detail_marker(fault)
424 if @progress_row_max > 0
425 output("=" * @progress_row_max, fault_color(fault))
426 else
427 nl
428 end
429 end
430
431 def output_progress_in_detail(fault)
432 return if @output_level == SILENT
433 nl
434 output_progress_in_detail_marker(fault)
435 if categorize_fault(fault) == :need_detail_faults
436 output_fault_in_detail(fault)
437 else
438 output_fault_in_short(fault)
439 end
440 output_progress_in_detail_marker(fault)
441 @progress_row = 0
442 end
443
444 def output?(level)
445 (level || @current_output_level) <= @output_level
446 end
447
448 def color(name)
449 _color = @color_scheme[name]
450 _color ||= @color_scheme["success"] if name == "pass"
451 _color ||= ColorScheme.default[name]
452 _color
453 end
454
455 def fault_color(fault)
456 fault_class_color(fault.class)
457 end
458
459 def fault_class_color(fault_class)
460 color(fault_class.name.split(/::/).last.downcase)
461 end
462
463 def result_color
464 color(@result.status)
465 end
466
467 def guess_color_availability
468 return false unless @output.tty?
469 case ENV["TERM"]
470 when /(?:term|screen)(?:-(?:256)?color)?\z/
471 true
472 else
473 return true if ENV["EMACS"] == "t"
474 false
475 end
476 end
477
478 def guess_progress_row_max
479 term_width = guess_term_width
480 if term_width.zero?
481 if ENV["EMACS"] == "t"
482 -1
483 else
484 79
485 end
486 else
487 term_width
488 end
489 end
490
491 def guess_term_width
492 Integer(ENV["COLUMNS"] || ENV["TERM_WIDTH"] || 0)
493 rescue ArgumentError
494 0
495 end
496 end
497
498 class ColorizedReadableDiffer < Diff::ReadableDiffer
499 def initialize(from, to, runner)
500 @runner = runner
501 super(from, to)
502 end
503
504 def need_diff?(options={})
505 operations.each do |tag,|
506 return true if [:replace, :equal].include?(tag)
507 end
508 false
509 end
510
511 private
512 def output_single(something, color=nil)
513 @runner.__send__(:output_single, something, color)
514 end
515
516 def output(something, color=nil)
517 @runner.__send__(:output, something, color)
518 end
519
520 def color(name)
521 @runner.__send__(:color, name)
522 end
523
524 def cut_off_ratio
525 0
526 end
527
528 def default_ratio
529 0
530 end
531
532 def tag(mark, color_name, contents)
533 _color = color(color_name)
534 contents.each do |content|
535 output_single(mark, _color)
536 output_single(" ")
537 output(content)
538 end
539 end
540
541 def tag_deleted(contents)
542 tag("-", "diff-deleted-tag", contents)
543 end
544
545 def tag_inserted(contents)
546 tag("+", "diff-inserted-tag", contents)
547 end
548
549 def tag_equal(contents)
550 tag(" ", "normal", contents)
551 end
552
553 def tag_difference(contents)
554 tag("?", "diff-difference-tag", contents)
555 end
556
557 def diff_line(from_line, to_line)
558 to_operations = []
559 from_line, to_line, _operations = line_operations(from_line, to_line)
560
561 no_replace = true
562 _operations.each do |tag,|
563 if tag == :replace
564 no_replace = false
565 break
566 end
567 end
568
569 output_single("?", color("diff-difference-tag"))
570 output_single(" ")
571 _operations.each do |tag, from_start, from_end, to_start, to_end|
572 from_width = compute_width(from_line, from_start, from_end)
573 to_width = compute_width(to_line, to_start, to_end)
574 case tag
575 when :replace
576 output_single(from_line[from_start...from_end],
577 color("diff-deleted"))
578 if (from_width < to_width)
579 output_single(" " * (to_width - from_width))
580 end
581 to_operations << Proc.new do
582 output_single(to_line[to_start...to_end],
583 color("diff-inserted"))
584 if (to_width < from_width)
585 output_single(" " * (from_width - to_width))
586 end
587 end
588 when :delete
589 output_single(from_line[from_start...from_end],
590 color("diff-deleted"))
591 unless no_replace
592 to_operations << Proc.new {output_single(" " * from_width)}
593 end
594 when :insert
595 if no_replace
596 output_single(to_line[to_start...to_end],
597 color("diff-inserted"))
598 else
599 output_single(" " * to_width)
600 to_operations << Proc.new do
601 output_single(to_line[to_start...to_end],
602 color("diff-inserted"))
603 end
604 end
605 when :equal
606 output_single(from_line[from_start...from_end])
607 unless no_replace
608 to_operations << Proc.new {output_single(" " * to_width)}
609 end
610 else
611 raise "unknown tag: #{tag}"
612 end
613 end
614 output("")
615
616 unless to_operations.empty?
617 output_single("?", color("diff-difference-tag"))
618 output_single(" ")
619 to_operations.each do |operation|
620 operation.call
621 end
622 output("")
623 end
624 end
625 end
626 end
627 end
628 end
629 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.0.8'
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
-148
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, t = [str1, str2].sort_by(&:length).
39 map{ |str| str.encode(Encoding::UTF_8).unpack("U*") }
40 n = s.length
41 m = t.length
42 big_int = n * m
43 return m if n.zero?
44 return n if m.zero?
45 return 0 if s == t
46
47 # If the length difference is already greater than the max_distance, then
48 # there is nothing else to check
49 if (n - m).abs >= max_distance
50 return max_distance
51 end
52
53 # The values necessary for our threshold are written; the ones after must
54 # be filled with large integers since the tailing member of the threshold
55 # window in the bottom array will run min across them
56 d = (m + 1).times.map { |i|
57 if i < m || i < max_distance + 1
58 i
59 else
60 big_int
61 end
62 }
63 x = nil
64 e = nil
65
66 n.times do |i|
67 # Since we're reusing arrays, we need to be sure to wipe the value left
68 # of the starting index; we don't have to worry about the value above the
69 # ending index as the arrays were initially filled with large integers
70 # and we progress to the right
71 if e.nil?
72 e = i + 1
73 else
74 e = big_int
75 end
76
77 diag_index = t.length - s.length + i
78
79 # If max_distance was specified, we can reduce second loop. So we set
80 # up our threshold window.
81 # See:
82 # Gusfield, Dan (1997). Algorithms on strings, trees, and sequences:
83 # computer science and computational biology.
84 # Cambridge, UK: Cambridge University Press. ISBN 0-521-58519-8.
85 # pp. 263–264.
86 min = [0, i - max_distance - 1].max
87 max = [m - 1, i + max_distance].min
88
89 (min .. max).each do |j|
90 # If the diagonal value is already greater than the max_distance
91 # then we can safety return: the diagonal will never go lower again.
92 # See: http://www.levenshtein.net/
93 if j == diag_index && d[j] >= max_distance
94 return max_distance
95 end
96
97 cost = s[i] == t[j] ? 0 : 1
98 x = [
99 d[j+1] + 1, # insertion
100 e + 1, # deletion
101 d[j] + cost # substitution
102 ].min
103
104 d[j] = e
105 e = x
106 end
107 d[m] = x
108 end
109
110 if x > max_distance
111 return max_distance
112 else
113 return x
114 end
115 end
116
117 def distance_without_maximum(str1, str2) # :nodoc:
118 s, t = [str1, str2].map{ |str| str.encode(Encoding::UTF_8).unpack("U*") }
119 n = s.length
120 m = t.length
121 return m if n.zero?
122 return n if m.zero?
123
124 d = (0..m).to_a
125 x = nil
126
127 n.times do |i|
128 e = i + 1
129 m.times do |j|
130 cost = (s[i] == t[j]) ? 0 : 1
131 x = [
132 d[j+1] + 1, # insertion
133 e + 1, # deletion
134 d[j] + cost # substitution
135 ].min
136 d[j] = e
137 e = x
138 end
139 d[m] = x
140 end
141
142 return x
143 end
144
145 extend self
146 end
147 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 = 0
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