Codebase list mikutter / 477289a
Imported Upstream version 3.0.0~alpha1+dfsg HIGUCHI Daisuke (VDR dai) 10 years ago
193 changed file(s) with 0 addition(s) and 26946 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
-8
vendor/bsearch/version.rb less more
0 module Bsearch
1 module VERSION #:nodoc:
2 MAJOR = 1
3 MINOR = 5
4 TINY = 0
5 STRING = [MAJOR, MINOR, TINY].compact.join('.')
6 end
7 end
+0
-117
vendor/bsearch.rb less more
0 #
1 # Ruby/Bsearch - a binary search library for Ruby.
2 #
3 # Copyright (C) 2001 Satoru Takabayashi <satoru@namazu.org>
4 # All rights reserved.
5 # This is free software with ABSOLUTELY NO WARRANTY.
6 #
7 # You can redistribute it and/or modify it under the terms of
8 # the Ruby's licence.
9 #
10 # Example:
11 #
12 # % irb -r ./bsearch.rb
13 # >> %w(a b c c c d e f).bsearch_first {|x| x <=> "c"}
14 # => 2
15 # >> %w(a b c c c d e f).bsearch_last {|x| x <=> "c"}
16 # => 4
17 # >> %w(a b c e f).bsearch_first {|x| x <=> "c"}
18 # => 2
19 # >> %w(a b e f).bsearch_first {|x| x <=> "c"}
20 # => nil
21 # >> %w(a b e f).bsearch_last {|x| x <=> "c"}
22 # => nil
23 # >> %w(a b e f).bsearch_lower_boundary {|x| x <=> "c"}
24 # => 2
25 # >> %w(a b e f).bsearch_upper_boundary {|x| x <=> "c"}
26 # => 2
27 # >> %w(a b c c c d e f).bsearch_range {|x| x <=> "c"}
28 # => 2...5
29 # >> %w(a b c d e f).bsearch_range {|x| x <=> "c"}
30 # => 2...3
31 # >> %w(a b d e f).bsearch_range {|x| x <=> "c"}
32 # => 2...2
33 $LOAD_PATH << File.dirname(File.expand_path(__FILE__))
34 require 'bsearch/version'
35 class Array
36 #
37 # The binary search algorithm is extracted from Jon Bentley's
38 # Programming Pearls 2nd ed. p.93
39 #
40
41 #
42 # Return the lower boundary. (inside)
43 #
44 def bsearch_lower_boundary (range = 0 ... self.length, &block)
45 lower = range.first() -1
46 upper = if range.exclude_end? then range.last else range.last + 1 end
47 while lower + 1 != upper
48 mid = ((lower + upper) / 2).to_i # for working with mathn.rb (Rational)
49 if yield(self[mid]) < 0
50 lower = mid
51 else
52 upper = mid
53 end
54 end
55 return upper
56 end
57
58 #
59 # This method searches the FIRST occurrence which satisfies a
60 # condition given by a block in binary fashion and return the
61 # index of the first occurrence. Return nil if not found.
62 #
63 def bsearch_first (range = 0 ... self.length, &block)
64 boundary = bsearch_lower_boundary(range, &block)
65 if boundary >= self.length || yield(self[boundary]) != 0
66 return nil
67 else
68 return boundary
69 end
70 end
71
72 alias bsearch bsearch_first
73
74 #
75 # Return the upper boundary. (outside)
76 #
77 def bsearch_upper_boundary (range = 0 ... self.length, &block)
78 lower = range.first() -1
79 upper = if range.exclude_end? then range.last else range.last + 1 end
80 while lower + 1 != upper
81 mid = ((lower + upper) / 2).to_i # for working with mathn.rb (Rational)
82 if yield(self[mid]) <= 0
83 lower = mid
84 else
85 upper = mid
86 end
87 end
88 return lower + 1 # outside of the matching range.
89 end
90
91 #
92 # This method searches the LAST occurrence which satisfies a
93 # condition given by a block in binary fashion and return the
94 # index of the last occurrence. Return nil if not found.
95 #
96 def bsearch_last (range = 0 ... self.length, &block)
97 # `- 1' for canceling `lower + 1' in bsearch_upper_boundary.
98 boundary = bsearch_upper_boundary(range, &block) - 1
99
100 if (boundary <= -1 || yield(self[boundary]) != 0)
101 return nil
102 else
103 return boundary
104 end
105 end
106
107 #
108 # Return the search result as a Range object.
109 #
110 def bsearch_range (range = 0 ... self.length, &block)
111 lower = bsearch_lower_boundary(range, &block)
112 upper = bsearch_upper_boundary(range, &block)
113 return lower ... upper
114 end
115 end
116
+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
-2
vendor/ruby_hmac.rb less more
0 # Convenience file to match gem name
1 require 'hmac'
+0
-25
vendor/test/unit/assertionfailederror.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
-2004
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-2012 Kouhei Sutou. All rights reserved.
3 # License:: Ruby license.
4
5 require 'test/unit/assertionfailederror'
6 require 'test/unit/util/backtracefilter'
7 require 'test/unit/util/method-owner-finder'
8 require 'test/unit/diff'
9
10 module Test
11 module Unit
12
13 ##
14 # Test::Unit::Assertions contains the standard Test::Unit assertions.
15 # Assertions is included in Test::Unit::TestCase.
16 #
17 # To include it in your own code and use its functionality, you simply
18 # need to rescue Test::Unit::AssertionFailedError. Additionally you may
19 # override add_assertion to get notified whenever an assertion is made.
20 #
21 # Notes:
22 # * The message to each assertion, if given, will be propagated with the
23 # failure.
24 # * It is easy to add your own assertions based on assert_block().
25 #
26 # = Example Custom Assertion
27 #
28 # def deny(boolean, message = nil)
29 # message = build_message message, '<?> is not false or nil.', boolean
30 # assert_block message do
31 # not boolean
32 # end
33 # end
34
35 module Assertions
36
37 ##
38 # The assertion upon which all other assertions are based. Passes if the
39 # block yields true.
40 #
41 # Example:
42 # assert_block "Couldn't do the thing" do
43 # do_the_thing
44 # end
45
46 public
47 def assert_block(message="assert_block failed.") # :yields:
48 _wrap_assertion do
49 if (! yield)
50 raise AssertionFailedError.new(message.to_s)
51 end
52 end
53 end
54
55 ##
56 # Asserts that +boolean+ is not false or nil.
57 #
58 # Example:
59 # assert [1, 2].include?(5)
60
61 public
62 def assert(boolean, message=nil)
63 _wrap_assertion do
64 assertion_message = nil
65 case message
66 when nil, String, Proc
67 when AssertionMessage
68 assertion_message = message
69 else
70 error_message = "assertion message must be String, Proc or "
71 error_message << "#{AssertionMessage}: "
72 error_message << "<#{message.inspect}>(<#{message.class}>)"
73 raise ArgumentError, error_message, filter_backtrace(caller)
74 end
75 assert_block("assert should not be called with a block.") do
76 !block_given?
77 end
78 assertion_message ||= build_message(message,
79 "<?> is not true.",
80 boolean)
81 assert_block(assertion_message) do
82 boolean
83 end
84 end
85 end
86
87 # Asserts that +object+ is false or nil.
88 #
89 # @note Just for minitest compatibility. :<
90 #
91 # @param [Object] object The object to be asserted.
92 # @return [void]
93 #
94 # @example Pass patterns
95 # refute(false) # => pass
96 # refute(nil) # => pass
97 #
98 # @example Fialure patterns
99 # refute(true) # => failure
100 # refute("string") # => failure
101 #
102 # @since 2.5.3
103 def refute(object, message=nil)
104 _wrap_assertion do
105 assertion_message = nil
106 case message
107 when nil, String, Proc
108 when AssertionMessage
109 assertion_message = message
110 else
111 error_message = "assertion message must be String, Proc or "
112 error_message << "#{AssertionMessage}: "
113 error_message << "<#{message.inspect}>(<#{message.class}>)"
114 raise ArgumentError, error_message, filter_backtrace(caller)
115 end
116 assert_block("refute should not be called with a block.") do
117 !block_given?
118 end
119 assertion_message ||= build_message(message,
120 "<?> is neither nil or false.",
121 object)
122 assert_block(assertion_message) do
123 not object
124 end
125 end
126 end
127
128 ##
129 # Passes if +expected+ == +actual+.
130 #
131 # Note that the ordering of arguments is important, since a helpful
132 # error message is generated when this one fails that tells you the
133 # values of expected and actual.
134 #
135 # Example:
136 # assert_equal 'MY STRING', 'my string'.upcase
137
138 public
139 def assert_equal(expected, actual, message=nil)
140 diff = AssertionMessage.delayed_diff(expected, actual)
141 if expected.respond_to?(:encoding) and
142 actual.respond_to?(:encoding) and
143 expected.encoding != actual.encoding
144 format = <<EOT
145 <?>(?) expected but was
146 <?>(?).?
147 EOT
148 full_message = build_message(message, format,
149 expected, expected.encoding.name,
150 actual, actual.encoding.name,
151 diff)
152 else
153 full_message = build_message(message, <<EOT, expected, actual, diff)
154 <?> expected but was
155 <?>.?
156 EOT
157 end
158 begin
159 assert_block(full_message) { expected == actual }
160 rescue AssertionFailedError => failure
161 _set_failed_information(failure, expected, actual, message)
162 raise failure # For JRuby. :<
163 end
164 end
165
166 ##
167 # Passes if the block raises one of the expected
168 # exceptions. When an expected exception is an Exception
169 # object, passes if expected_exception == actual_exception.
170 #
171 # Example:
172 # assert_raise(RuntimeError, LoadError) do
173 # raise 'Boom!!!'
174 # end # -> pass
175 #
176 # assert_raise do
177 # raise Exception, 'Any exception should be raised!!!'
178 # end # -> pass
179 #
180 # assert_raise(RuntimeError.new("XXX")) {raise "XXX"} # -> pass
181 # assert_raise(MyError.new("XXX")) {raise "XXX"} # -> fail
182 # assert_raise(RuntimeError.new("ZZZ")) {raise "XXX"} # -> fail
183 public
184 def assert_raise(*args, &block)
185 assert_expected_exception = Proc.new do |*_args|
186 message, assert_exception_helper, actual_exception = _args
187 expected = assert_exception_helper.expected_exceptions
188 diff = AssertionMessage.delayed_diff(expected, actual_exception)
189 full_message = build_message(message,
190 "<?> exception expected but was\n<?>.?",
191 expected, actual_exception, diff)
192 begin
193 assert_block(full_message) do
194 expected == [] or
195 assert_exception_helper.expected?(actual_exception)
196 end
197 rescue AssertionFailedError => failure
198 _set_failed_information(failure, expected, actual_exception,
199 message)
200 raise failure # For JRuby. :<
201 end
202 end
203 _assert_raise(assert_expected_exception, *args, &block)
204 end
205
206 # Just for minitest compatibility. :<
207 alias_method :assert_raises, :assert_raise
208
209 ##
210 # Passes if the block raises one of the given
211 # exceptions or sub exceptions of the given exceptions.
212 #
213 # Example:
214 # assert_raise_kind_of(SystemCallError) do
215 # raise Errno::EACCES
216 # end
217 def assert_raise_kind_of(*args, &block)
218 assert_expected_exception = Proc.new do |*_args|
219 message, assert_exception_helper, actual_exception = _args
220 expected = assert_exception_helper.expected_exceptions
221 full_message = build_message(message,
222 "<?> family exception expected " +
223 "but was\n<?>.",
224 expected, actual_exception)
225 assert_block(full_message) do
226 assert_exception_helper.expected?(actual_exception, :kind_of?)
227 end
228 end
229 _assert_raise(assert_expected_exception, *args, &block)
230 end
231
232
233 ##
234 # Passes if +object+.instance_of?(+klass+). When +klass+ is
235 # an array of classes, it passes if any class
236 # satisfies +object.instance_of?(class).
237 #
238 # Example:
239 # assert_instance_of(String, 'foo') # -> pass
240 # assert_instance_of([Fixnum, NilClass], 100) # -> pass
241 # assert_instance_of([Numeric, NilClass], 100) # -> fail
242
243 public
244 def assert_instance_of(klass, object, message="")
245 _wrap_assertion do
246 klasses = nil
247 klasses = klass if klass.is_a?(Array)
248 assert_block("The first parameter to assert_instance_of should be " +
249 "a Class or an Array of Class.") do
250 if klasses
251 klasses.all? {|k| k.is_a?(Class)}
252 else
253 klass.is_a?(Class)
254 end
255 end
256 klass_message = AssertionMessage.maybe_container(klass) do |value|
257 "<#{value}>"
258 end
259 full_message = build_message(message, <<EOT, object, klass_message, object.class)
260 <?> expected to be an instance of
261 ? but was
262 <?>.
263 EOT
264 assert_block(full_message) do
265 if klasses
266 klasses.any? {|k| object.instance_of?(k)}
267 else
268 object.instance_of?(klass)
269 end
270 end
271 end
272 end
273
274 ##
275 # Passes if +object+ is nil.
276 #
277 # Example:
278 # assert_nil [1, 2].uniq!
279
280 public
281 def assert_nil(object, message="")
282 full_message = build_message(message, <<EOT, object)
283 <?> expected to be nil.
284 EOT
285 assert_block(full_message) { object.nil? }
286 end
287
288 ##
289 # Passes if +object+.kind_of?(+klass+). When +klass+ is
290 # an array of classes or modules, it passes if any
291 # class or module satisfies +object.kind_of?(class_or_module).
292 #
293 # Example:
294 # assert_kind_of(Object, 'foo') # -> pass
295 # assert_kind_of([Fixnum, NilClass], 100) # -> pass
296 # assert_kind_of([Fixnum, NilClass], "string") # -> fail
297
298 public
299 def assert_kind_of(klass, object, message="")
300 _wrap_assertion do
301 klasses = nil
302 klasses = klass if klass.is_a?(Array)
303 assert_block("The first parameter to assert_kind_of should be " +
304 "a kind_of Module or an Array of a kind_of Module.") do
305 if klasses
306 klasses.all? {|k| k.kind_of?(Module)}
307 else
308 klass.kind_of?(Module)
309 end
310 end
311 klass_message = AssertionMessage.maybe_container(klass) do |value|
312 "<#{value}>"
313 end
314 full_message = build_message(message,
315 "<?> expected to be kind_of\\?\n" +
316 "? but was\n" +
317 "<?>.",
318 object,
319 klass_message,
320 object.class)
321 assert_block(full_message) do
322 if klasses
323 klasses.any? {|k| object.kind_of?(k)}
324 else
325 object.kind_of?(klass)
326 end
327 end
328 end
329 end
330
331 ##
332 # Passes if +object+ .respond_to? +method+
333 #
334 # Example:
335 # assert_respond_to 'bugbear', :slice
336
337 public
338 def assert_respond_to(object, method, message="")
339 _wrap_assertion do
340 full_message = build_message(message,
341 "<?>.kind_of\\?(Symbol) or\n" +
342 "<?>.respond_to\\?(:to_str) expected",
343 method, method)
344 assert_block(full_message) do
345 method.kind_of?(Symbol) or method.respond_to?(:to_str)
346 end
347 full_message = build_message(message,
348 "<?>.respond_to\\?(?) expected\n" +
349 "(Class: <?>)",
350 object, method, object.class)
351 assert_block(full_message) {object.respond_to?(method)}
352 end
353 end
354
355 ##
356 # Passes if +object+ does not .respond_to? +method+.
357 #
358 # Example:
359 # assert_not_respond_to('bugbear', :nonexistence) # -> pass
360 # assert_not_respond_to('bugbear', :size) # -> fail
361
362 public
363 def assert_not_respond_to(object, method, message="")
364 _wrap_assertion do
365 full_message = build_message(message,
366 "<?>.kind_of\\?(Symbol) or\n" +
367 "<?>.respond_to\\?(:to_str) expected",
368 method, method)
369 assert_block(full_message) do
370 method.kind_of?(Symbol) or method.respond_to?(:to_str)
371 end
372 full_message = build_message(message,
373 "!<?>.respond_to\\?(?) expected\n" +
374 "(Class: <?>)",
375 object, method, object.class)
376 assert_block(full_message) {!object.respond_to?(method)}
377 end
378 end
379
380 # Just for minitest compatibility. :<
381 #
382 # @since 2.5.3
383 alias_method :refute_respond_to, :assert_not_respond_to
384
385 ##
386 # Passes if +string+ =~ +pattern+.
387 #
388 # Example:
389 # assert_match(/\d+/, 'five, 6, seven')
390
391 public
392 def assert_match(pattern, string, message="")
393 _wrap_assertion do
394 pattern = case(pattern)
395 when String
396 Regexp.new(Regexp.escape(pattern))
397 else
398 pattern
399 end
400 full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
401 assert_block(full_message) { string =~ pattern }
402 end
403 end
404
405 ##
406 # Passes if +actual+ .equal? +expected+ (i.e. they are the same
407 # instance).
408 #
409 # Example:
410 # o = Object.new
411 # assert_same o, o
412
413 public
414 def assert_same(expected, actual, message="")
415 full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
416 <?>
417 with id <?> expected to be equal\\? to
418 <?>
419 with id <?>.
420 EOT
421 assert_block(full_message) { actual.equal?(expected) }
422 end
423
424 ##
425 # Compares the +object1+ with +object2+ using +operator+.
426 #
427 # Passes if object1.__send__(operator, object2) is true.
428 #
429 # Example:
430 # assert_operator 5, :>=, 4
431
432 public
433 def assert_operator(object1, operator, object2, message="")
434 _wrap_assertion do
435 full_message = build_message(nil, "<?>\ngiven as the operator for #assert_operator must be a Symbol or #respond_to\\?(:to_str).", operator)
436 assert_block(full_message){operator.kind_of?(Symbol) || operator.respond_to?(:to_str)}
437 full_message = build_message(message, <<EOT, object1, AssertionMessage.literal(operator), object2)
438 <?> expected to be
439 ?
440 <?>.
441 EOT
442 assert_block(full_message) { object1.__send__(operator, object2) }
443 end
444 end
445
446 ##
447 # Passes if block does not raise an exception.
448 #
449 # Example:
450 # assert_nothing_raised do
451 # [1, 2].uniq
452 # end
453
454 public
455 def assert_nothing_raised(*args)
456 _wrap_assertion do
457 if args.last.is_a?(String)
458 message = args.pop
459 else
460 message = ""
461 end
462
463 assert_exception_helper = AssertExceptionHelper.new(self, args)
464 begin
465 yield
466 rescue Exception => e
467 if ((args.empty? && !e.instance_of?(AssertionFailedError)) ||
468 assert_exception_helper.expected?(e))
469 failure_message = build_message(message, "Exception raised:\n?", e)
470 assert_block(failure_message) {false}
471 else
472 raise
473 end
474 end
475 nil
476 end
477 end
478
479 ##
480 # Flunk always fails.
481 #
482 # Example:
483 # flunk 'Not done testing yet.'
484
485 public
486 def flunk(message="Flunked")
487 assert_block(build_message(message)){false}
488 end
489
490 ##
491 # Passes if ! +actual+ .equal? +expected+
492 #
493 # Example:
494 # assert_not_same Object.new, Object.new
495
496 public
497 def assert_not_same(expected, actual, message="")
498 full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
499 <?>
500 with id <?> expected to not be equal\\? to
501 <?>
502 with id <?>.
503 EOT
504 assert_block(full_message) { !actual.equal?(expected) }
505 end
506
507 # Just for minitest compatibility. :<
508 #
509 # @since 2.5.3
510 alias_method :refute_same, :assert_not_same
511
512 ##
513 # Passes if +expected+ != +actual+
514 #
515 # Example:
516 # assert_not_equal 'some string', 5
517
518 public
519 def assert_not_equal(expected, actual, message="")
520 full_message = build_message(message, "<?> expected to be != to\n<?>.", expected, actual)
521 assert_block(full_message) { expected != actual }
522 end
523
524 # Just for minitest compatibility. :<
525 #
526 # @since 2.5.3
527 alias_method :refute_equal, :assert_not_equal
528
529 ##
530 # Passes if ! +object+ .nil?
531 #
532 # Example:
533 # assert_not_nil '1 two 3'.sub!(/two/, '2')
534
535 public
536 def assert_not_nil(object, message="")
537 full_message = build_message(message, "<?> expected to not be nil.", object)
538 assert_block(full_message){!object.nil?}
539 end
540
541 # Just for minitest compatibility. :<
542 #
543 # @since 2.5.3
544 alias_method :refute_nil, :assert_not_nil
545
546 ##
547 # Passes if +regexp+ !~ +string+
548 #
549 # Example:
550 # assert_not_match(/two/, 'one 2 three') # -> pass
551 # assert_not_match(/three/, 'one 2 three') # -> fail
552
553 public
554 def assert_not_match(regexp, string, message="")
555 _wrap_assertion do
556 assert_instance_of(Regexp, regexp,
557 "<REGEXP> in assert_not_match(<REGEXP>, ...) " +
558 "should be a Regexp.")
559 full_message = build_message(message,
560 "<?> expected to not match\n<?>.",
561 regexp, string)
562 assert_block(full_message) { regexp !~ string }
563 end
564 end
565
566 # Just for minitest compatibility. :<
567 #
568 # @since 2.5.3
569 alias_method :refute_match, :assert_not_match
570
571 ##
572 # Deprecated. Use #assert_not_match instead.
573 #
574 # Passes if +regexp+ !~ +string+
575 #
576 # Example:
577 # assert_no_match(/two/, 'one 2 three') # -> pass
578 # assert_no_match(/three/, 'one 2 three') # -> fail
579
580 public
581 def assert_no_match(regexp, string, message="")
582 _wrap_assertion do
583 assert_instance_of(Regexp, regexp,
584 "The first argument to assert_no_match " +
585 "should be a Regexp.")
586 assert_not_match(regexp, string, message)
587 end
588 end
589
590 UncaughtThrow = {
591 NameError => /^uncaught throw \`(.+)\'$/, #`
592 ArgumentError => /^uncaught throw (.+)$/,
593 ThreadError => /^uncaught throw \`(.+)\' in thread / #`
594 }
595
596 ##
597 # Passes if the block throws +expected_object+
598 #
599 # Example:
600 # assert_throw(:done) do
601 # throw(:done)
602 # end
603
604 public
605 def assert_throw(expected_object, message="", &proc)
606 _wrap_assertion do
607 begin
608 catch([]) {}
609 rescue TypeError
610 assert_instance_of(Symbol, expected_object,
611 "assert_throws expects the symbol that should be thrown for its first argument")
612 end
613 assert_block("Should have passed a block to assert_throw.") do
614 block_given?
615 end
616 caught = true
617 begin
618 catch(expected_object) do
619 proc.call
620 caught = false
621 end
622 full_message = build_message(message,
623 "<?> should have been thrown.",
624 expected_object)
625 assert_block(full_message) {caught}
626 rescue NameError, ArgumentError, ThreadError => error
627 raise unless UncaughtThrow[error.class] =~ error.message
628 tag = $1
629 tag = tag[1..-1].intern if tag[0, 1] == ":"
630 full_message = build_message(message,
631 "<?> expected to be thrown but\n" +
632 "<?> was thrown.",
633 expected_object, tag)
634 flunk(full_message)
635 end
636 end
637 end
638
639 # Just for minitest compatibility. :<
640 #
641 # @since 2.5.3
642 alias_method :assert_throws, :assert_throw
643
644 ##
645 # Passes if block does not throw anything.
646 #
647 # Example:
648 # assert_nothing_thrown do
649 # [1, 2].uniq
650 # end
651
652 public
653 def assert_nothing_thrown(message="", &proc)
654 _wrap_assertion do
655 assert(block_given?, "Should have passed a block to assert_nothing_thrown")
656 begin
657 proc.call
658 rescue NameError, ArgumentError, ThreadError => error
659 raise unless UncaughtThrow[error.class] =~ error.message
660 tag = $1
661 tag = tag[1..-1].intern if tag[0, 1] == ":"
662 full_message = build_message(message,
663 "<?> was thrown when nothing was expected",
664 tag)
665 flunk(full_message)
666 end
667 assert(true, "Expected nothing to be thrown")
668 end
669 end
670
671 ##
672 # Passes if +expected_float+ and +actual_float+ are equal
673 # within +delta+ tolerance.
674 #
675 # Example:
676 # assert_in_delta 0.05, (50000.0 / 10**6), 0.00001
677
678 public
679 def assert_in_delta(expected_float, actual_float, delta=0.001, message="")
680 _wrap_assertion do
681 _assert_in_delta_validate_arguments(expected_float,
682 actual_float,
683 delta)
684 full_message = _assert_in_delta_message(expected_float,
685 actual_float,
686 delta,
687 message)
688 assert_block(full_message) do
689 (expected_float.to_f - actual_float.to_f).abs <= delta.to_f
690 end
691 end
692 end
693
694 ##
695 # Passes if +expected_float+ and +actual_float+ are
696 # not equal within +delta+ tolerance.
697 #
698 # Example:
699 # assert_not_in_delta(0.05, (50000.0 / 10**6), 0.00002) # -> pass
700 # assert_not_in_delta(0.05, (50000.0 / 10**6), 0.00001) # -> fail
701
702 public
703 def assert_not_in_delta(expected_float, actual_float, delta=0.001, message="")
704 _wrap_assertion do
705 _assert_in_delta_validate_arguments(expected_float,
706 actual_float,
707 delta)
708 full_message = _assert_in_delta_message(expected_float,
709 actual_float,
710 delta,
711 message,
712 :negative_assertion => true)
713 assert_block(full_message) do
714 (expected_float.to_f - actual_float.to_f).abs > delta.to_f
715 end
716 end
717 end
718
719 # Just for minitest compatibility. :<
720 #
721 # @since 2.5.3
722 alias_method :refute_in_delta, :assert_not_in_delta
723
724 # :stopdoc:
725 private
726 def _assert_in_delta_validate_arguments(expected_float,
727 actual_float,
728 delta)
729 {
730 expected_float => "first float",
731 actual_float => "second float",
732 delta => "delta"
733 }.each do |float, name|
734 assert_respond_to(float, :to_f,
735 "The arguments must respond to to_f; " +
736 "the #{name} did not")
737 end
738 delta = delta.to_f
739 assert_operator(delta, :>=, 0.0, "The delta should not be negative")
740 end
741
742 def _assert_in_delta_message(expected_float, actual_float, delta,
743 message, options={})
744 if options[:negative_assertion]
745 format = <<-EOT
746 <?> -/+ <?> expected to not include
747 <?>.
748 EOT
749 else
750 format = <<-EOT
751 <?> -/+ <?> expected to include
752 <?>.
753 EOT
754 end
755 arguments = [expected_float, delta, actual_float]
756 normalized_expected = expected_float.to_f
757 normalized_actual = actual_float.to_f
758 normalized_delta = delta.to_f
759 relation_format = nil
760 relation_arguments = nil
761 if normalized_actual < normalized_expected - normalized_delta
762 relation_format = "<<?> < <?>-<?>[?] <= <?>+<?>[?]>"
763 relation_arguments = [actual_float,
764 expected_float, delta,
765 normalized_expected - normalized_delta,
766 expected_float, delta,
767 normalized_expected + normalized_delta]
768 elsif normalized_actual <= normalized_expected + normalized_delta
769 relation_format = "<<?>-<?>[?] <= <?> <= <?>+<?>[?]>"
770 relation_arguments = [expected_float, delta,
771 normalized_expected - normalized_delta,
772 actual_float,
773 expected_float, delta,
774 normalized_expected + normalized_delta]
775 else
776 relation_format = "<<?>-<?>[?] <= <?>+<?>[?] < <?>>"
777 relation_arguments = [expected_float, delta,
778 normalized_expected - normalized_delta,
779 expected_float, delta,
780 normalized_expected + normalized_delta,
781 actual_float]
782 end
783
784 if relation_format
785 format << <<-EOT
786
787 Relation:
788 #{relation_format}
789 EOT
790 arguments.concat(relation_arguments)
791 end
792
793 build_message(message, format, *arguments)
794 end
795
796 public
797 # :startdoc:
798
799 ##
800 # Passes if +expected_float+ and +actual_float+ are equal
801 # within +epsilon+ relative error of +expected_float+.
802 #
803 # Example:
804 # assert_in_epsilon(10000.0, 9900.0, 0.1) # -> pass
805 # assert_in_epsilon(10000.0, 9899.0, 0.1) # -> fail
806
807 public
808 def assert_in_epsilon(expected_float, actual_float, epsilon=0.001,
809 message="")
810 _wrap_assertion do
811 _assert_in_epsilon_validate_arguments(expected_float,
812 actual_float,
813 epsilon)
814 full_message = _assert_in_epsilon_message(expected_float,
815 actual_float,
816 epsilon,
817 message)
818 assert_block(full_message) do
819 normalized_expected_float = expected_float.to_f
820 if normalized_expected_float.zero?
821 delta = epsilon.to_f ** 2
822 else
823 delta = normalized_expected_float * epsilon.to_f
824 end
825 delta = delta.abs
826 (normalized_expected_float - actual_float.to_f).abs <= delta
827 end
828 end
829 end
830
831 ##
832 # Passes if +expected_float+ and +actual_float+ are
833 # not equal within +epsilon+ relative error of
834 # +expected_float+.
835 #
836 # Example:
837 # assert_not_in_epsilon(10000.0, 9900.0, 0.1) # -> fail
838 # assert_not_in_epsilon(10000.0, 9899.0, 0.1) # -> pass
839
840 public
841 def assert_not_in_epsilon(expected_float, actual_float, epsilon=0.001,
842 message="")
843 _wrap_assertion do
844 _assert_in_epsilon_validate_arguments(expected_float,
845 actual_float,
846 epsilon)
847 full_message = _assert_in_epsilon_message(expected_float,
848 actual_float,
849 epsilon,
850 message,
851 :negative_assertion => true)
852 assert_block(full_message) do
853 normalized_expected_float = expected_float.to_f
854 delta = normalized_expected_float * epsilon.to_f
855 (normalized_expected_float - actual_float.to_f).abs > delta
856 end
857 end
858 end
859
860 # :stopdoc:
861 private
862 def _assert_in_epsilon_validate_arguments(expected_float,
863 actual_float,
864 epsilon)
865 {
866 expected_float => "first float",
867 actual_float => "second float",
868 epsilon => "epsilon"
869 }.each do |float, name|
870 assert_respond_to(float, :to_f,
871 "The arguments must respond to to_f; " +
872 "the #{name} did not")
873 end
874 epsilon = epsilon.to_f
875 assert_operator(epsilon, :>=, 0.0, "The epsilon should not be negative")
876 end
877
878 def _assert_in_epsilon_message(expected_float, actual_float, epsilon,
879 message, options={})
880 normalized_expected = expected_float.to_f
881 normalized_actual = actual_float.to_f
882 normalized_epsilon = epsilon.to_f
883 delta = normalized_expected * normalized_epsilon
884
885 if options[:negative_assertion]
886 format = <<-EOT
887 <?> -/+ (<?> * <?>)[?] expected to not include
888 <?>.
889 EOT
890 else
891 format = <<-EOT
892 <?> -/+ (<?> * <?>)[?] expected to include
893 <?>.
894 EOT
895 end
896 arguments = [expected_float, expected_float, epsilon, delta,
897 actual_float]
898
899 relation_format = nil
900 relation_arguments = nil
901 if normalized_actual < normalized_expected - delta
902 relation_format = "<<?> < <?>-(<?>*<?>)[?] <= <?>+(<?>*<?>)[?]>"
903 relation_arguments = [actual_float,
904 expected_float, expected_float, epsilon,
905 normalized_expected - delta,
906 expected_float, expected_float, epsilon,
907 normalized_expected + delta]
908 elsif normalized_actual <= normalized_expected + delta
909 relation_format = "<<?>-(<?>*<?>)[?] <= <?> <= <?>+(<?>*<?>)[?]>"
910 relation_arguments = [expected_float, expected_float, epsilon,
911 normalized_expected - delta,
912 actual_float,
913 expected_float, expected_float, epsilon,
914 normalized_expected + delta]
915 else
916 relation_format = "<<?>-(<?>*<?>)[?] <= <?>+(<?>*<?>)[?] < <?>>"
917 relation_arguments = [expected_float, expected_float, epsilon,
918 normalized_expected - delta,
919 expected_float, expected_float, epsilon,
920 normalized_expected + delta,
921 actual_float]
922 end
923
924 if relation_format
925 format << <<-EOT
926
927 Relation:
928 #{relation_format}
929 EOT
930 arguments.concat(relation_arguments)
931 end
932
933 build_message(message, format, *arguments)
934 end
935
936 public
937 # :startdoc:
938
939 ##
940 # Passes if the method send returns a true value.
941 #
942 # +send_array+ is composed of:
943 # * A receiver
944 # * A method
945 # * Arguments to the method
946 #
947 # Example:
948 # assert_send([[1, 2], :member?, 1]) # -> pass
949 # assert_send([[1, 2], :member?, 4]) # -> fail
950
951 public
952 def assert_send(send_array, message=nil)
953 _wrap_assertion do
954 assert_instance_of(Array, send_array,
955 "assert_send requires an array " +
956 "of send information")
957 assert_operator(send_array.size, :>=, 2,
958 "assert_send requires at least a receiver " +
959 "and a message name")
960 format = <<EOT
961 <?> expected to respond to
962 <?(*?)> with a true value but was
963 <?>.
964 EOT
965 receiver, message_name, *arguments = send_array
966 result = nil
967 full_message =
968 build_message(message,
969 format,
970 receiver,
971 AssertionMessage.literal(message_name.to_s),
972 arguments,
973 AssertionMessage.delayed_literal {result})
974 assert_block(full_message) do
975 result = receiver.__send__(message_name, *arguments)
976 result
977 end
978 end
979 end
980
981 ##
982 # Passes if the method send doesn't return a true value.
983 #
984 # +send_array+ is composed of:
985 # * A receiver
986 # * A method
987 # * Arguments to the method
988 #
989 # Example:
990 # assert_not_send([[1, 2], :member?, 1]) # -> fail
991 # assert_not_send([[1, 2], :member?, 4]) # -> pass
992 def assert_not_send(send_array, message=nil)
993 _wrap_assertion do
994 assert_instance_of(Array, send_array,
995 "assert_not_send requires an array " +
996 "of send information")
997 assert_operator(send_array.size, :>=, 2,
998 "assert_not_send requires at least a receiver " +
999 "and a message name")
1000 format = <<EOT
1001 <?> expected to respond to
1002 <?(*?)> with not a true value but was
1003 <?>.
1004 EOT
1005 receiver, message_name, *arguments = send_array
1006 result = nil
1007 full_message =
1008 build_message(message,
1009 format,
1010 receiver,
1011 AssertionMessage.literal(message_name.to_s),
1012 arguments,
1013 AssertionMessage.delayed_literal {result})
1014 assert_block(full_message) do
1015 result = receiver.__send__(message_name, *arguments)
1016 not result
1017 end
1018 end
1019 end
1020
1021 ##
1022 # Passes if +actual+ is a boolean value.
1023 #
1024 # Example:
1025 # assert_boolean(true) # -> pass
1026 # assert_boolean(nil) # -> fail
1027 def assert_boolean(actual, message=nil)
1028 _wrap_assertion do
1029 assert_block(build_message(message,
1030 "<true> or <false> expected but was\n<?>",
1031 actual)) do
1032 [true, false].include?(actual)
1033 end
1034 end
1035 end
1036
1037 ##
1038 # Passes if +actual+ is true.
1039 #
1040 # Example:
1041 # assert_true(true) # -> pass
1042 # assert_true(:true) # -> fail
1043 def assert_true(actual, message=nil)
1044 _wrap_assertion do
1045 assert_block(build_message(message,
1046 "<true> expected but was\n<?>",
1047 actual)) do
1048 actual == true
1049 end
1050 end
1051 end
1052
1053 ##
1054 # Passes if +actual+ is false.
1055 #
1056 # Example:
1057 # assert_false(false) # -> pass
1058 # assert_false(nil) # -> fail
1059 def assert_false(actual, message=nil)
1060 _wrap_assertion do
1061 assert_block(build_message(message,
1062 "<false> expected but was\n<?>",
1063 actual)) do
1064 actual == false
1065 end
1066 end
1067 end
1068
1069 ##
1070 # Passes if expression "+expected+ +operator+
1071 # +actual+" is true.
1072 #
1073 # Example:
1074 # assert_compare(1, "<", 10) # -> pass
1075 # assert_compare(1, ">=", 10) # -> fail
1076 def assert_compare(expected, operator, actual, message=nil)
1077 _wrap_assertion do
1078 assert_send([["<", "<=", ">", ">="], :include?, operator.to_s])
1079 case operator.to_s
1080 when "<"
1081 operator_description = "less than"
1082 when "<="
1083 operator_description = "less than or equal to"
1084 when ">"
1085 operator_description = "greater than"
1086 when ">="
1087 operator_description = "greater than or equal to"
1088 end
1089 template = <<-EOT
1090 <?> #{operator} <?> should be true
1091 <?> expected #{operator_description}
1092 <?>.
1093 EOT
1094 full_message = build_message(message, template,
1095 expected, actual,
1096 expected, actual)
1097 assert_block(full_message) do
1098 expected.__send__(operator, actual)
1099 end
1100 end
1101 end
1102
1103 ##
1104 # Passes if assertion is failed in block.
1105 #
1106 # Example:
1107 # assert_fail_assertion {assert_equal("A", "B")} # -> pass
1108 # assert_fail_assertion {assert_equal("A", "A")} # -> fail
1109 def assert_fail_assertion(message=nil)
1110 _wrap_assertion do
1111 full_message = build_message(message,
1112 "Failed assertion was expected.")
1113 assert_block(full_message) do
1114 begin
1115 yield
1116 false
1117 rescue AssertionFailedError
1118 true
1119 end
1120 end
1121 end
1122 end
1123
1124 ##
1125 # Passes if an exception is raised in block and its
1126 # message is +expected+.
1127 #
1128 # Example:
1129 # assert_raise_message("exception") {raise "exception"} # -> pass
1130 # assert_raise_message(/exc/i) {raise "exception"} # -> pass
1131 # assert_raise_message("exception") {raise "EXCEPTION"} # -> fail
1132 # assert_raise_message("exception") {} # -> fail
1133 def assert_raise_message(expected, message=nil)
1134 _wrap_assertion do
1135 full_message = build_message(message,
1136 "<?> exception message expected " +
1137 "but none was thrown.",
1138 expected)
1139 exception = nil
1140 assert_block(full_message) do
1141 begin
1142 yield
1143 false
1144 rescue Exception => exception
1145 true
1146 end
1147 end
1148
1149 actual = exception.message
1150 diff = AssertionMessage.delayed_diff(expected, actual)
1151 full_message =
1152 build_message(message,
1153 "<?> exception message expected but was\n" +
1154 "<?>.?", expected, actual, diff)
1155 assert_block(full_message) do
1156 if expected.is_a?(Regexp)
1157 expected =~ actual
1158 else
1159 expected == actual
1160 end
1161 end
1162 end
1163 end
1164
1165 ##
1166 # Passes if +object+.const_defined?(+constant_name+)
1167 #
1168 # Example:
1169 # assert_const_defined(Test, :Unit) # -> pass
1170 # assert_const_defined(Object, :Nonexistent) # -> fail
1171 def assert_const_defined(object, constant_name, message=nil)
1172 _wrap_assertion do
1173 full_message = build_message(message,
1174 "<?>.const_defined\\?(<?>) expected.",
1175 object, constant_name)
1176 assert_block(full_message) do
1177 object.const_defined?(constant_name)
1178 end
1179 end
1180 end
1181
1182 ##
1183 # Passes if !+object+.const_defined?(+constant_name+)
1184 #
1185 # Example:
1186 # assert_not_const_defined(Object, :Nonexistent) # -> pass
1187 # assert_not_const_defined(Test, :Unit) # -> fail
1188 def assert_not_const_defined(object, constant_name, message=nil)
1189 _wrap_assertion do
1190 full_message = build_message(message,
1191 "!<?>.const_defined\\?(<?>) expected.",
1192 object, constant_name)
1193 assert_block(full_message) do
1194 !object.const_defined?(constant_name)
1195 end
1196 end
1197 end
1198
1199 ##
1200 # Passes if +object+.+predicate+ is _true_.
1201 #
1202 # Example:
1203 # assert_predicate([], :empty?) # -> pass
1204 # assert_predicate([1], :empty?) # -> fail
1205 def assert_predicate(object, predicate, message=nil)
1206 _wrap_assertion do
1207 assert_respond_to(object, predicate, message)
1208 actual = object.__send__(predicate)
1209 full_message = build_message(message,
1210 "<?>.? is true value expected but was\n" +
1211 "<?>",
1212 object,
1213 AssertionMessage.literal(predicate),
1214 actual)
1215 assert_block(full_message) do
1216 actual
1217 end
1218 end
1219 end
1220
1221 ##
1222 # Passes if +object+.+predicate+ is not _true_.
1223 #
1224 # Example:
1225 # assert_not_predicate([1], :empty?) # -> pass
1226 # assert_not_predicate([], :empty?) # -> fail
1227 def assert_not_predicate(object, predicate, message=nil)
1228 _wrap_assertion do
1229 assert_respond_to(object, predicate, message)
1230 actual = object.__send__(predicate)
1231 full_message = build_message(message,
1232 "<?>.? is false value expected but was\n" +
1233 "<?>",
1234 object,
1235 AssertionMessage.literal(predicate),
1236 actual)
1237 assert_block(full_message) do
1238 not actual
1239 end
1240 end
1241 end
1242
1243 ##
1244 # Passes if +object+#+alias_name+ is an alias method of
1245 # +object+#+original_name+.
1246 #
1247 # Example:
1248 # assert_alias_method([], :length, :size) # -> pass
1249 # assert_alias_method([], :size, :length) # -> pass
1250 # assert_alias_method([], :each, :size) # -> fail
1251 def assert_alias_method(object, alias_name, original_name, message=nil)
1252 _wrap_assertion do
1253 find_method_failure_message = Proc.new do |method_name|
1254 build_message(message,
1255 "<?>.? doesn't exist\n" +
1256 "(Class: <?>)",
1257 object,
1258 AssertionMessage.literal(method_name),
1259 object.class)
1260 end
1261
1262 alias_method = original_method = nil
1263 assert_block(find_method_failure_message.call(alias_name)) do
1264 begin
1265 alias_method = object.method(alias_name)
1266 true
1267 rescue NameError
1268 false
1269 end
1270 end
1271 assert_block(find_method_failure_message.call(original_name)) do
1272 begin
1273 original_method = object.method(original_name)
1274 true
1275 rescue NameError
1276 false
1277 end
1278 end
1279
1280 full_message = build_message(message,
1281 "<?> is alias of\n" +
1282 "<?> expected",
1283 alias_method,
1284 original_method)
1285 assert_block(full_message) do
1286 alias_method == original_method
1287 end
1288 end
1289 end
1290
1291 ##
1292 # Passes if +path+ exists.
1293 #
1294 # Example:
1295 # assert_path_exist("/tmp") # -> pass
1296 # assert_path_exist("/bin/sh") # -> pass
1297 # assert_path_exist("/nonexistent") # -> fail
1298 def assert_path_exist(path, message=nil)
1299 _wrap_assertion do
1300 failure_message = build_message(message,
1301 "<?> expected to exist",
1302 path)
1303 assert_block(failure_message) do
1304 File.exist?(path)
1305 end
1306 end
1307 end
1308
1309 ##
1310 # Passes if +path+ doesn't exist.
1311 #
1312 # Example:
1313 # assert_path_not_exist("/nonexistent") # -> pass
1314 # assert_path_not_exist("/tmp") # -> fail
1315 # assert_path_not_exist("/bin/sh") # -> fail
1316 def assert_path_not_exist(path, message=nil)
1317 _wrap_assertion do
1318 failure_message = build_message(message,
1319 "<?> expected to not exist",
1320 path)
1321 assert_block(failure_message) do
1322 not File.exist?(path)
1323 end
1324 end
1325 end
1326
1327 ##
1328 # Passes if +collection+ includes +object+.
1329 #
1330 # Example:
1331 # assert_include([1, 10], 1) # -> pass
1332 # assert_include(1..10, 5) # -> pass
1333 # assert_include([1, 10], 5) # -> fail
1334 # assert_include(1..10, 20) # -> fail
1335 def assert_include(collection, object, message=nil)
1336 _wrap_assertion do
1337 assert_respond_to(collection, :include?,
1338 "The collection must respond to :include?.")
1339 full_message = build_message(message,
1340 "<?> expected to include\n<?>.",
1341 collection,
1342 object)
1343 assert_block(full_message) do
1344 collection.include?(object)
1345 end
1346 end
1347 end
1348
1349 # Just for minitest compatibility. :<
1350 #
1351 # @since 2.5.3
1352 alias_method :assert_includes, :assert_include
1353
1354 ##
1355 # Passes if +collection+ doesn't include +object+.
1356 #
1357 # Example:
1358 # assert_not_include([1, 10], 5) # -> pass
1359 # assert_not_include(1..10, 20) # -> pass
1360 # assert_not_include([1, 10], 1) # -> fail
1361 # assert_not_include(1..10, 5) # -> fail
1362 def assert_not_include(collection, object, message=nil)
1363 _wrap_assertion do
1364 assert_respond_to(collection, :include?,
1365 "The collection must respond to :include?.")
1366 full_message = build_message(message,
1367 "<?> expected to not include\n<?>.",
1368 collection,
1369 object)
1370 assert_block(full_message) do
1371 not collection.include?(object)
1372 end
1373 end
1374 end
1375
1376 ##
1377 # Passes if +object+ is empty.
1378 #
1379 # Example:
1380 # assert_empty("") # -> pass
1381 # assert_empty([]) # -> pass
1382 # assert_empty({}) # -> pass
1383 # assert_empty(" ") # -> fail
1384 # assert_empty([nil]) # -> fail
1385 # assert_empty({1 => 2}) # -> fail
1386 def assert_empty(object, message=nil)
1387 _wrap_assertion do
1388 assert_respond_to(object, :empty?,
1389 "The object must respond to :empty?.")
1390 full_message = build_message(message,
1391 "<?> expected to be empty.",
1392 object)
1393 assert_block(full_message) do
1394 object.empty?
1395 end
1396 end
1397 end
1398
1399 ##
1400 # Passes if +object+ is not empty.
1401 #
1402 # Example:
1403 # assert_not_empty(" ") # -> pass
1404 # assert_not_empty([nil]) # -> pass
1405 # assert_not_empty({1 => 2}) # -> pass
1406 # assert_not_empty("") # -> fail
1407 # assert_not_empty([]) # -> fail
1408 # assert_not_empty({}) # -> fail
1409 def assert_not_empty(object, message=nil)
1410 _wrap_assertion do
1411 assert_respond_to(object, :empty?,
1412 "The object must respond to :empty?.")
1413 full_message = build_message(message,
1414 "<?> expected to not be empty.",
1415 object)
1416 assert_block(full_message) do
1417 not object.empty?
1418 end
1419 end
1420 end
1421
1422 ##
1423 # Builds a failure message. +head+ is added before the +template+ and
1424 # +arguments+ replaces the '?'s positionally in the template.
1425
1426 public
1427 def build_message(head, template=nil, *arguments)
1428 template &&= template.chomp
1429 return AssertionMessage.new(head, template, arguments)
1430 end
1431
1432 private
1433 def _wrap_assertion(&block)
1434 @_assertion_wrapped ||= false
1435 if @_assertion_wrapped
1436 block.call
1437 else
1438 @_assertion_wrapped = true
1439 begin
1440 add_assertion
1441 block.call
1442 ensure
1443 @_assertion_wrapped = false
1444 end
1445 end
1446 end
1447
1448 public
1449 # Called whenever an assertion is made. Define this in classes
1450 # that include Test::Unit::Assertions to record assertion
1451 # counts.
1452 #
1453 # This is a public API for developers who extend test-unit.
1454 #
1455 # @return [void]
1456 def add_assertion
1457 end
1458
1459 ##
1460 # Select whether or not to use the pretty-printer. If this option is set
1461 # to false before any assertions are made, pp.rb will not be required.
1462
1463 public
1464 def self.use_pp=(value)
1465 AssertionMessage.use_pp = value
1466 end
1467
1468 # :stopdoc:
1469 private
1470 def _assert_raise(assert_expected_exception, *args, &block)
1471 _wrap_assertion do
1472 if args.last.is_a?(String)
1473 message = args.pop
1474 else
1475 message = ""
1476 end
1477
1478 assert_exception_helper = AssertExceptionHelper.new(self, args)
1479 expected = assert_exception_helper.expected_exceptions
1480 actual_exception = nil
1481 full_message = build_message(message,
1482 "<?> exception expected " +
1483 "but none was thrown.",
1484 expected)
1485 assert_block(full_message) do
1486 begin
1487 yield
1488 false
1489 rescue Exception => actual_exception
1490 true
1491 end
1492 end
1493 assert_expected_exception.call(message, assert_exception_helper,
1494 actual_exception)
1495 actual_exception
1496 end
1497 end
1498
1499 private
1500 def _set_failed_information(failure, expected, actual, user_message)
1501 failure.expected = expected
1502 failure.actual = actual
1503 failure.inspected_expected = AssertionMessage.convert(expected)
1504 failure.inspected_actual = AssertionMessage.convert(actual)
1505 failure.user_message = user_message
1506 end
1507
1508 class AssertionMessage
1509 @use_pp = true
1510 class << self
1511 attr_accessor :use_pp
1512
1513 def literal(value)
1514 Literal.new(value)
1515 end
1516
1517 def delayed_literal(&block)
1518 DelayedLiteral.new(block)
1519 end
1520
1521 def maybe_container(value, &formatter)
1522 MaybeContainer.new(value, &formatter)
1523 end
1524
1525 MAX_DIFF_TARGET_STRING_SIZE = 1000
1526 def max_diff_target_string_size
1527 return @@max_diff_target_string_size if @@max_diff_target_string_size
1528
1529 size = ENV["TEST_UNIT_MAX_DIFF_TARGET_STRING_SIZE"]
1530 if size
1531 begin
1532 size = Integer(size)
1533 rescue ArgumentError
1534 size = nil
1535 end
1536 end
1537 size || MAX_DIFF_TARGET_STRING_SIZE
1538 end
1539
1540 @@max_diff_target_string_size = nil
1541 def max_diff_target_string_size=(size)
1542 @@max_diff_target_string_size = size
1543 end
1544
1545 def diff_target_string?(string)
1546 if string.respond_to?(:bytesize)
1547 string.bytesize < max_diff_target_string_size
1548 else
1549 string.size < max_diff_target_string_size
1550 end
1551 end
1552
1553 def ensure_diffable_string(string)
1554 if string.respond_to?(:encoding) and
1555 !string.encoding.ascii_compatible?
1556 string = string.dup.force_encoding("ASCII-8BIT")
1557 end
1558 string
1559 end
1560
1561 def prepare_for_diff(from, to)
1562 if !from.is_a?(String) or !to.is_a?(String)
1563 from = convert(from)
1564 to = convert(to)
1565 end
1566
1567 if diff_target_string?(from) and diff_target_string?(to)
1568 from = ensure_diffable_string(from)
1569 to = ensure_diffable_string(to)
1570 [from, to]
1571 else
1572 [nil, nil]
1573 end
1574 end
1575
1576 def delayed_diff(from, to)
1577 delayed_literal do
1578 from, to = prepare_for_diff(from, to)
1579
1580 diff = "" if from.nil? or to.nil?
1581 diff ||= Diff.readable(from, to)
1582 if /^[-+]/ !~ diff
1583 diff = ""
1584 elsif /^[ ?]/ =~ diff or /(?:.*\n){2,}/ =~ diff
1585 diff = "\n\ndiff:\n#{diff}"
1586 else
1587 diff = ""
1588 end
1589
1590 if Diff.need_fold?(diff)
1591 folded_diff = Diff.folded_readable(from, to)
1592 diff << "\n\nfolded diff:\n#{folded_diff}"
1593 end
1594
1595 diff
1596 end
1597 end
1598
1599 def convert(object)
1600 if object.is_a?(Exception)
1601 object = AssertExceptionHelper::WrappedException.new(object)
1602 end
1603 inspector = Inspector.new(object)
1604 if use_pp
1605 begin
1606 require 'pp' unless defined?(PP)
1607 begin
1608 return PP.pp(inspector, '').chomp
1609 rescue NameError
1610 end
1611 rescue LoadError
1612 self.use_pp = false
1613 end
1614 end
1615 inspector.inspect
1616 end
1617 end
1618
1619 class Inspector
1620 include Comparable
1621
1622 class << self
1623 def cached_new(object, inspected_objects)
1624 inspected_objects[object.object_id] ||=
1625 new(object, inspected_objects)
1626 end
1627
1628 @@inspector_classes = []
1629 def inspector_classes
1630 @@inspector_classes
1631 end
1632
1633 def register_inspector_class(inspector_class)
1634 @@inspector_classes << inspector_class
1635 end
1636
1637 def unregister_inspector_class(inspector_class)
1638 @@inspector_classes.delete(inspector_class)
1639 end
1640 end
1641
1642 attr_reader :object
1643 def initialize(object, inspected_objects={})
1644 @inspected_objects = inspected_objects
1645 @object = object
1646 @inspected_objects[@object.object_id] = self
1647 @inspect_target = inspect_target
1648 end
1649
1650 alias_method :native_inspect, :inspect
1651 def inspect
1652 @inspect_target.inspect
1653 end
1654
1655 def pretty_print(q)
1656 @inspect_target.pretty_print(q)
1657 end
1658
1659 def pretty_print_cycle(q)
1660 @inspect_target.pretty_print_cycle(q)
1661 end
1662
1663 def <=>(other)
1664 if other.is_a?(self.class)
1665 @object <=> other.object
1666 else
1667 @object <=> other
1668 end
1669 end
1670
1671 private
1672 def inspect_target
1673 self.class.inspector_classes.each do |inspector_class|
1674 if inspector_class.target?(@object)
1675 return inspector_class.new(@object, @inspected_objects)
1676 end
1677 end
1678 @object
1679 end
1680 end
1681
1682 class HashInspector
1683 Inspector.register_inspector_class(self)
1684
1685 class << self
1686 def target?(object)
1687 object.is_a?(Hash) or ENV.equal?(object)
1688 end
1689 end
1690
1691 def initialize(hash, inspected_objects)
1692 @inspected_objects = inspected_objects
1693 @hash = {}
1694 hash.each do |key, value|
1695 key = Inspector.cached_new(key, @inspected_objects)
1696 value = Inspector.cached_new(value, @inspected_objects)
1697 @hash[key] = value
1698 end
1699 end
1700
1701 def inspect
1702 @hash.inspect
1703 end
1704
1705 def pretty_print(q)
1706 q.group(1, '{', '}') do
1707 q.seplist(self, nil, :each_pair) do |k, v|
1708 q.group do
1709 q.pp(k)
1710 q.text('=>')
1711 q.group(1) do
1712 q.breakable('')
1713 q.pp(v)
1714 end
1715 end
1716 end
1717 end
1718 end
1719
1720 def pretty_print_cycle(q)
1721 @hash.pretty_print_cycle(q)
1722 end
1723
1724 def each_pair
1725 keys = @hash.keys
1726 begin
1727 keys = keys.sort # FIXME: more cleverly
1728 rescue ArgumentError
1729 end
1730 keys.each do |key|
1731 yield(key, @hash[key])
1732 end
1733 end
1734 end
1735
1736 class ArrayInspector
1737 Inspector.register_inspector_class(self)
1738
1739 class << self
1740 def target?(object)
1741 object.is_a?(Array)
1742 end
1743 end
1744
1745 def initialize(array, inspected_objects)
1746 @inspected_objects = inspected_objects
1747 @array = array.collect do |element|
1748 Inspector.cached_new(element, @inspected_objects)
1749 end
1750 end
1751
1752 def inspect
1753 @array.inspect
1754 end
1755
1756 def pretty_print(q)
1757 q.group(1, '[', ']') do
1758 q.seplist(self) do |v|
1759 q.pp(v)
1760 end
1761 end
1762 end
1763
1764 def pretty_print_cycle(q)
1765 @array.pretty_print_cycle(q)
1766 end
1767
1768 def each(&block)
1769 @array.each(&block)
1770 end
1771 end
1772
1773 class Literal
1774 def initialize(value)
1775 @value = value
1776 end
1777
1778 def inspect
1779 @value.to_s
1780 end
1781 end
1782
1783 class DelayedLiteral
1784 def initialize(value)
1785 @value = value
1786 end
1787
1788 def inspect
1789 @value.call.to_s
1790 end
1791 end
1792
1793 class MaybeContainer
1794 def initialize(value, &formatter)
1795 @value = value
1796 @formatter = formatter
1797 end
1798
1799 def inspect
1800 if @value.is_a?(Array)
1801 values = @value.collect do |value|
1802 @formatter.call(AssertionMessage.convert(value))
1803 end
1804 "[#{values.join(', ')}]"
1805 else
1806 @formatter.call(AssertionMessage.convert(@value))
1807 end
1808 end
1809 end
1810
1811 class Template
1812 def self.create(string)
1813 parts = (string ? string.scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/m) : [])
1814 self.new(parts)
1815 end
1816
1817 attr_reader :count
1818
1819 def initialize(parts)
1820 @parts = parts
1821 @count = parts.find_all{|e| e == '?'}.size
1822 end
1823
1824 def result(parameters)
1825 raise "The number of parameters does not match the number of substitutions." if(parameters.size != count)
1826 params = parameters.dup
1827 expanded_template = ""
1828 @parts.each do |part|
1829 if part == '?'
1830 encoding_safe_concat(expanded_template, params.shift)
1831 else
1832 expanded_template << part.gsub(/\\\?/m, '?')
1833 end
1834 end
1835 expanded_template
1836 end
1837
1838 private
1839 if Object.const_defined?(:Encoding)
1840 def encoding_safe_concat(buffer, parameter)
1841 if Encoding.compatible?(buffer, parameter)
1842 buffer << parameter
1843 else
1844 buffer << parameter.dup.force_encoding(buffer.encoding)
1845 end
1846 end
1847 else
1848 def encoding_safe_concat(buffer, parameter)
1849 buffer << parameter
1850 end
1851 end
1852 end
1853
1854 include Util::BacktraceFilter
1855
1856 def initialize(head, template_string, parameters)
1857 @head = head
1858 @template_string = template_string
1859 @parameters = parameters
1860 end
1861
1862 def convert(object)
1863 self.class.convert(object)
1864 end
1865
1866 def template
1867 @template ||= Template.create(@template_string)
1868 end
1869
1870 def add_period(string)
1871 (string =~ /\.\Z/ ? string : string + '.')
1872 end
1873
1874 def to_s
1875 message_parts = []
1876 if (@head)
1877 head = @head.to_s
1878 unless(head.empty?)
1879 message_parts << add_period(head)
1880 end
1881 end
1882 tail = template.result(@parameters.collect{|e| convert(e)})
1883 message_parts << tail unless(tail.empty?)
1884 message_parts.join("\n")
1885 end
1886 end
1887
1888 class AssertExceptionHelper
1889 class WrappedException
1890 attr_reader :exception
1891 def initialize(exception)
1892 @exception = exception
1893 end
1894
1895 def inspect
1896 if default_inspect?
1897 "#{@exception.class.inspect}(<#{@exception.message}>)"
1898 else
1899 @exception.inspect
1900 end
1901 end
1902
1903 def method_missing(name, *args, &block)
1904 @exception.__send__(name, *args, &block)
1905 end
1906
1907 private
1908 def default_inspect?
1909 inspect_method = @exception.method(:inspect)
1910 if inspect_method.respond_to?(:owner) and
1911 inspect_method.owner == Exception
1912 true
1913 else
1914 default_inspect_method = Exception.instance_method(:inspect)
1915 default_inspect_method.bind(@exception).call == @exception.inspect
1916 end
1917 end
1918 end
1919
1920 def initialize(test_case, expected_exceptions)
1921 @test_case = test_case
1922 @expected_exceptions = expected_exceptions
1923 @expected_classes, @expected_modules, @expected_objects =
1924 split_expected_exceptions(expected_exceptions)
1925 end
1926
1927 def expected_exceptions
1928 exceptions = @expected_exceptions.collect do |exception|
1929 if exception.is_a?(Exception)
1930 WrappedException.new(exception)
1931 else
1932 exception
1933 end
1934 end
1935 if exceptions.size == 1
1936 exceptions[0]
1937 else
1938 exceptions
1939 end
1940 end
1941
1942 def expected?(actual_exception, equality=nil)
1943 equality ||= :instance_of?
1944 expected_class?(actual_exception, equality) or
1945 expected_module?(actual_exception) or
1946 expected_object?(actual_exception)
1947 end
1948
1949 private
1950 def split_expected_exceptions(expected_exceptions)
1951 exception_modules = []
1952 exception_objects = []
1953 exception_classes = []
1954 expected_exceptions.each do |exception_type|
1955 if exception_type.instance_of?(Module)
1956 exception_modules << exception_type
1957 elsif exception_type.is_a?(Exception)
1958 exception_objects << exception_type
1959 else
1960 @test_case.__send__(:assert,
1961 Exception >= exception_type,
1962 "Should expect a class of exception, " +
1963 "#{exception_type}")
1964 exception_classes << exception_type
1965 end
1966 end
1967 [exception_classes, exception_modules, exception_objects]
1968 end
1969
1970 def expected_class?(actual_exception, equality)
1971 @expected_classes.any? do |expected_class|
1972 actual_exception.__send__(equality, expected_class)
1973 end
1974 end
1975
1976 def expected_module?(actual_exception)
1977 @expected_modules.any? do |expected_module|
1978 actual_exception.is_a?(expected_module)
1979 end
1980 end
1981
1982 def expected_object?(actual_exception)
1983 @expected_objects.any? do |expected_object|
1984 expected_object == actual_exception or
1985 fallback_exception_object_equal(expected_object, actual_exception)
1986 end
1987 end
1988
1989 def fallback_exception_object_equal(expected_object, actual_exception)
1990 owner = Util::MethodOwnerFinder.find(expected_object, :==)
1991 if owner == Kernel or owner == Exception
1992 expected_object.class == actual_exception.class and
1993 expected_object.message == actual_exception.message
1994 else
1995 false
1996 end
1997 end
1998 end
1999
2000 # :startdoc:
2001 end
2002 end
2003 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
-129
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)
11 super(self.class.stringify(key))
12 end
13
14 def []=(key, value)
15 super(self.class.stringify(key), value)
16 end
17 end
18
19 class << self
20 def included(base)
21 base.extend(BaseClassMethods)
22 base.extend(ClassMethods)
23 end
24 end
25
26 module BaseClassMethods
27 def attributes_table
28 {}
29 end
30 end
31
32 module ClassMethods
33 def method_added(name)
34 super
35 return unless defined?(@current_attributes)
36
37 attributes = {}
38 kept_attributes = StringifyKeyHash.new
39 @current_attributes.each do |attribute_name, attribute|
40 attributes[attribute_name] = attribute[:value]
41 kept_attributes[attribute_name] = attribute if attribute[:keep]
42 end
43 set_attributes(name, attributes)
44 @current_attributes = kept_attributes
45 end
46
47 def attribute(name, value, options={}, *method_names)
48 unless options.is_a?(Hash)
49 method_names << options
50 options = {}
51 end
52 if method_names.empty?
53 current_attributes[name] = options.merge(:value => value)
54 else
55 method_names.each do |method_name|
56 set_attributes(method_name, {name => value})
57 end
58 end
59 end
60
61 def current_attributes
62 @current_attributes ||= StringifyKeyHash.new
63 end
64
65 def current_attribute(name)
66 current_attributes[name] || StringifyKeyHash.new
67 end
68
69 def attributes_table
70 @attributes_table ||= StringifyKeyHash.new
71 super.merge(@attributes_table)
72 end
73
74 def set_attributes(method_name, new_attributes)
75 return if new_attributes.empty?
76 @attributes_table ||= StringifyKeyHash.new
77 @attributes_table[method_name] ||= StringifyKeyHash.new
78 current_attributes = @attributes_table[method_name]
79 new_attributes.each do |key, value|
80 observers = attribute_observers(key) || []
81 observers.each do |observer|
82 observer.call(self,
83 StringifyKeyHash.stringify(key),
84 (attributes(method_name) || {})[key],
85 value,
86 method_name)
87 end
88 current_attributes[key] = value
89 end
90 end
91
92 def attributes(method_name)
93 attributes = attributes_table[method_name]
94 ancestors[1..-1].each do |ancestor|
95 if ancestor.is_a?(Class) and ancestor < Test::Unit::Attribute
96 parent_attributes = ancestor.attributes(method_name)
97 if attributes
98 attributes = (parent_attributes || {}).merge(attributes)
99 else
100 attributes = parent_attributes
101 end
102 break
103 end
104 end
105 attributes || StringifyKeyHash.new
106 end
107
108 @@attribute_observers = StringifyKeyHash.new
109 def register_attribute_observer(attribute_name, observer=Proc.new)
110 @@attribute_observers[attribute_name] ||= []
111 @@attribute_observers[attribute_name] << observer
112 end
113
114 def attribute_observers(attribute_name)
115 @@attribute_observers[attribute_name]
116 end
117 end
118
119 def attributes
120 self.class.attributes(@method_name) || StringifyKeyHash.new
121 end
122
123 def [](name)
124 attributes[name]
125 end
126 end
127 end
128 end
+0
-474
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 "(patterns may be used).") 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 "(patterns may be used).") 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 "(patterns may be used).") do |n|
241 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
242 case n
243 when Regexp
244 @filters << proc{|t| n =~ t.class.name ? true : false}
245 else
246 @filters << proc{|t| n == t.class.name}
247 end
248 end
249
250 o.on('--ignore-testcase=TESTCASE', String,
251 "Ignores tests in TestCases matching TESTCASE.",
252 "(patterns may be used).") do |n|
253 n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
254 case n
255 when Regexp
256 @filters << proc {|t| n =~ t.class.name ? false : true}
257 else
258 @filters << proc {|t| n != t.class.name}
259 end
260 end
261
262 o.on('--location=LOCATION', String,
263 "Runs tests that defined in LOCATION.",
264 "LOCATION is one of PATH:LINE, PATH or LINE") do |location|
265 if /\A\d+\z/ =~ location
266 path = nil
267 line = location.to_i
268 else
269 path, line, = location.split(/:(\d+)/, 2)
270 line = line.to_i unless line.nil?
271 end
272 @filters << lambda do |test|
273 test.class.test_defined?(:path => path,
274 :line => line,
275 :method_name => test.method_name)
276 end
277 end
278
279 o.on('--attribute=EXPRESSION', String,
280 "Runs tests that matches EXPRESSION.",
281 "EXPRESSION is evaluated as Ruby's expression.",
282 "Test attribute name can be used with no receiver in EXPRESSION.",
283 "EXPRESSION examples:",
284 " !slow",
285 " tag == 'important' and !slow") do |expression|
286 @filters << lambda do |test|
287 matcher = AttributeMatcher.new(test)
288 matcher.match?(expression)
289 end
290 end
291
292 priority_filter = Proc.new do |test|
293 if @filters == [priority_filter]
294 Priority::Checker.new(test).need_to_run?
295 else
296 nil
297 end
298 end
299 o.on("--[no-]priority-mode",
300 "Runs some tests based on their priority.") do |priority_mode|
301 if priority_mode
302 Priority.enable
303 @filters |= [priority_filter]
304 else
305 Priority.disable
306 @filters -= [priority_filter]
307 end
308 end
309
310 o.on("--default-priority=PRIORITY",
311 Priority.available_values,
312 "Uses PRIORITY as default priority",
313 "(#{keyword_display(Priority.available_values)})") do |priority|
314 Priority.default = priority
315 end
316
317 o.on('-I', "--load-path=DIR[#{File::PATH_SEPARATOR}DIR...]",
318 "Appends directory list to $LOAD_PATH.") do |dirs|
319 $LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
320 end
321
322 color_schemes = ColorScheme.all
323 o.on("--color-scheme=SCHEME", color_schemes,
324 "Use SCHEME as color scheme.",
325 "(#{keyword_display(color_schemes)})") do |scheme|
326 @color_scheme = scheme
327 end
328
329 o.on("--config=FILE",
330 "Use YAML fomat FILE content as configuration file.") do |file|
331 load_config(file)
332 end
333
334 o.on("--order=ORDER", TestCase::AVAILABLE_ORDERS,
335 "Run tests in a test case in ORDER order.",
336 "(#{keyword_display(TestCase::AVAILABLE_ORDERS)})") do |order|
337 TestCase.test_order = order
338 end
339
340 assertion_message_class = Test::Unit::Assertions::AssertionMessage
341 o.on("--max-diff-target-string-size=SIZE", Integer,
342 "Shows diff if both expected result string size and " +
343 "actual result string size are " +
344 "less than or equal SIZE in bytes.",
345 "(#{assertion_message_class.max_diff_target_string_size})") do |size|
346 assertion_message_class.max_diff_target_string_size = size
347 end
348
349 ADDITIONAL_OPTIONS.each do |option_builder|
350 option_builder.call(self, o)
351 end
352
353 o.on('--',
354 "Stop processing options so that the",
355 "remaining options will be passed to the",
356 "test."){o.terminate}
357
358 o.on('-h', '--help', 'Display this help.'){puts o; exit}
359
360 o.on_tail
361 o.on_tail('Deprecated options:')
362
363 o.on_tail('--console', 'Console runner (use --runner).') do
364 warn("Deprecated option (--console).")
365 @runner = self.class.runner(:console)
366 end
367
368 if RUNNERS[:fox]
369 o.on_tail('--fox', 'Fox runner (use --runner).') do
370 warn("Deprecated option (--fox).")
371 @runner = self.class.runner(:fox)
372 end
373 end
374
375 o.on_tail
376 end
377 end
378
379 def keyword_display(keywords)
380 keywords = keywords.collect do |keyword, _|
381 keyword.to_s
382 end.uniq.sort
383
384 i = 0
385 keywords.collect do |keyword|
386 if (i > 0 and keyword[0] == keywords[i - 1][0]) or
387 ((i < keywords.size - 1) and (keyword[0] == keywords[i + 1][0]))
388 n = 2
389 else
390 n = 1
391 end
392 i += 1
393 keyword.sub(/^(.{#{n}})([A-Za-z]+)(?=\w*$)/, '\\1[\\2]')
394 end.join(", ")
395 end
396
397 def run
398 self.class.need_auto_run = false
399 suite = @collector[self]
400 return false if suite.nil?
401 return true if suite.empty?
402 runner = @runner[self]
403 return false if runner.nil?
404 @runner_options[:color_scheme] ||= @color_scheme
405 @runner_options[:listeners] ||= []
406 @runner_options[:listeners].concat(@listeners)
407 change_work_directory do
408 runner.run(suite, @runner_options).passed?
409 end
410 end
411
412 def load_config(file)
413 require 'yaml'
414 config = YAML.load(File.read(file))
415 runner_name = config["runner"]
416 @runner = self.class.runner(runner_name) || @runner
417 @collector = self.class.collector(config["collector"]) || @collector
418 (config["color_schemes"] || {}).each do |name, options|
419 ColorScheme[name] = options
420 end
421 runner_options = {}
422 (config["#{runner_name}_options"] || {}).each do |key, value|
423 key = key.to_sym
424 value = ColorScheme[value] if key == :color_scheme
425 if key == :arguments
426 @default_arguments.concat(value.split)
427 else
428 runner_options[key.to_sym] = value
429 end
430 end
431 @runner_options = @runner_options.merge(runner_options)
432 end
433
434 private
435 def default_runner
436 runner = self.class.default_runner
437 if ENV["EMACS"] == "t"
438 runner ||= self.class.runner(:emacs)
439 else
440 runner ||= self.class.runner(:console)
441 end
442 runner
443 end
444
445 def default_collector
446 self.class.collector(@standalone ? :load : :descendant)
447 end
448
449 def global_config_file
450 File.expand_path("~/.test-unit.yml")
451 rescue ArgumentError
452 nil
453 end
454
455 def load_global_config
456 file = global_config_file
457 load_config(file) if file and File.exist?(file)
458 end
459
460 def change_work_directory(&block)
461 if @workdir
462 Dir.chdir(@workdir, &block)
463 else
464 yield
465 end
466 end
467 end
468 end
469 end
470
471 require 'test/unit/runner/console'
472 require 'test/unit/runner/emacs'
473 require 'test/unit/runner/xml'
+0
-32
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(file, line, options={})
8 n_context_line = options[:n_context_line] || 3
9 lines = source(file)
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(file)
21 @sources[file] ||= read_source(file)
22 end
23
24 private
25 def read_source(file)
26 return nil unless File.exist?(file)
27 File.readlines(file)
28 end
29 end
30 end
31 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
-185
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 $LOAD_PATH.unshift(path.to_s) if path
122 yield
123 ensure
124 $LOAD_PATH.delete_at($LOAD_PATH.index(path.to_s)) if path
125 end
126
127 def excluded_directory?(base)
128 @system_directory_excludes.any? {|pattern| pattern =~ base}
129 end
130
131 def excluded_file?(base)
132 return true if @system_excludes.any? {|pattern| pattern =~ base}
133
134 patterns = @patterns || []
135 unless patterns.empty?
136 return true unless patterns.any? {|pattern| pattern =~ base}
137 end
138
139 excludes = @excludes || []
140 unless excludes.empty?
141 return true if excludes.any? {|pattern| pattern =~ base}
142 end
143
144 false
145 end
146
147 def add_require_failed_test_suite(test_suites)
148 return if @require_failed_infos.empty?
149
150 require_failed_infos = @require_failed_infos
151 require_failed_omissions = Class.new(Test::Unit::TestCase)
152 require_failed_omissions.class_eval do
153 class << self
154 def name
155 "RequireFailedOmissions"
156 end
157 end
158
159 require_failed_infos.each do |info|
160 path = info[:path]
161 normalized_path = path.to_s.gsub(/[^a-z0-9\_]+/i, '_')
162 normalized_path = normalized_path.gsub(/\A_+/, '')
163 exception = info[:exception]
164 define_method("test_require_#{normalized_path}") do
165 @require_failed_exception = exception
166 omit("failed to load: <#{path}>: <#{exception.message}>")
167 end
168 end
169
170 def priority
171 100
172 end
173
174 def filter_backtrace(location)
175 super(@require_failed_exception.backtrace)
176 end
177 end
178
179 add_suite(test_suites, require_failed_omissions.suite)
180 end
181 end
182 end
183 end
184 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
-70
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 |descendant_test_case|
27 parent = descendant_test_case.ancestors[1]
28 children_map[parent] ||= []
29 children_map[parent] << descendant_test_case
30 end
31
32 root_test_cases = children_map.keys - test_cases
33 root_test_cases.each do |root_test_case|
34 add_test_case(suite, root_test_case, children_map)
35 end
36 end
37
38 def include?(test)
39 return true if(@filters.empty?)
40 @filters.each do |filter|
41 return false if filter[test] == false
42 end
43 true
44 end
45
46 def sort(suites)
47 suites.sort_by do |suite|
48 [suite.priority, suite.name || suite.to_s]
49 end
50 end
51
52 private
53 def add_test_case(suite, test_case, children_map)
54 children = children_map[test_case]
55 return if children.nil?
56
57 sub_suites = []
58 children.each do |child|
59 sub_suite = child.suite
60 add_test_case(sub_suite, child, children_map)
61 add_suite(sub_suites, sub_suite)
62 end
63 sort(sub_suites).each do |sub_suite|
64 suite << sub_suite
65 end
66 end
67 end
68 end
69 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/exceptionhandler.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
-95
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 module Test
5 module Unit
6 class FaultLocationDetector
7 def initialize(fault, code_snippet_fetcher)
8 @fault = fault
9 @code_snippet_fetcher = code_snippet_fetcher
10 extract_fault_information
11 end
12
13 def split_backtrace_entry(entry)
14 match_data = /\A(.+):(\d+)(?::(.*))?\z/.match(entry)
15 return nil if match_data.nil?
16 file, line_number, context = match_data.to_a[1..-1]
17 line_number = line_number.to_i
18 if /\Ain `(.+?)'/ =~ context
19 method_name = $1
20 else
21 method_name = nil
22 end
23 [file, line_number, context, method_name]
24 end
25
26 def target?(backtrace_entry)
27 file, line_number, context, method_name =
28 split_backtrace_entry(backtrace_entry)
29 _ = context
30 return false if file.nil?
31
32 if @fault_source_location
33 target_source_location?(file, line_number, method_name)
34 elsif @fault_method_name
35 target_method?(method_name)
36 else
37 true
38 end
39 end
40
41 private
42 def target_source_location?(file, line_number, method_name)
43 fault_file, fault_line_number = @fault_source_location
44 return false unless file.end_with?(fault_file)
45
46 return false if line_number < fault_line_number
47
48 lines = @code_snippet_fetcher.source(file)
49 return false if lines.nil?
50
51 base_indent_level = nil
52 fault_line_number.step(lines.size) do |current_line_number|
53 line = lines[current_line_number - 1]
54 current_indent_level = guess_indent_level(line)
55 base_indent_level ||= current_indent_level
56 return true if current_line_number == line_number
57
58 if current_line_number == fault_line_number
59 break if /(?:\send|\})\s*$/ =~ line
60 else
61 break if current_indent_level == base_indent_level
62 end
63 end
64 false
65 end
66
67 def target_method?(method_name)
68 @fault_method_name == method_name
69 end
70
71 def guess_indent_level(line)
72 if /\A(\s*)/ =~ line
73 $1.sub(/\t/, " " * 8).count(" ")
74 else
75 0
76 end
77 end
78
79 def extract_fault_information
80 if @fault.respond_to?(:source_location)
81 @fault_source_location = @fault.source_location
82 else
83 @fault_source_location = nil
84 end
85
86 if @fault.respond_to?(:method_name)
87 @fault_method_name = @fault.method_name
88 else
89 @fault_method_name = nil
90 end
91 end
92 end
93 end
94 end
+0
-227
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 |fixture|
8 observer = lambda do |test_case, _, _, value, callback|
9 if value.nil?
10 test_case.send("unregister_#{fixture}_callback", callback)
11 else
12 test_case.send("register_#{fixture}_callback", callback, value)
13 end
14 end
15 base.register_attribute_observer(fixture, &observer)
16 end
17 end
18 end
19
20 module ClassMethods
21 def setup(*method_names, &callback)
22 register_fixture(:setup, *method_names, &callback)
23 end
24
25 def unregister_setup(*method_names_or_callbacks)
26 unregister_fixture(:setup, *method_names_or_callbacks)
27 end
28
29 def cleanup(*method_names, &callback)
30 register_fixture(:cleanup, *method_names, &callback)
31 end
32
33 def unregister_cleanup(*method_names_or_callbacks)
34 unregister_fixture(:cleanup, *method_names_or_callbacks)
35 end
36
37 def teardown(*method_names, &callback)
38 register_fixture(:teardown, *method_names, &callback)
39 end
40
41 def unregister_teardown(*method_names_or_callbacks)
42 unregister_fixture(:teardown, *method_names_or_callbacks)
43 end
44
45 def register_setup_callback(method_name_or_callback, options)
46 register_fixture_callback(:setup, method_name_or_callback,
47 options, :after, :append)
48 end
49
50 def unregister_setup_callback(method_name_or_callback)
51 unregister_fixture_callback(:setup, method_name_or_callback)
52 end
53
54 def register_cleanup_callback(method_name_or_callback, options)
55 register_fixture_callback(:cleanup, method_name_or_callback,
56 options, :before, :prepend)
57 end
58
59 def unregister_cleanup_callback(method_name_or_callback)
60 unregister_fixture_callback(:cleanup, method_name_or_callback)
61 end
62
63 def register_teardown_callback(method_name_or_callback, options)
64 register_fixture_callback(:teardown, method_name_or_callback,
65 options, :before, :prepend)
66 end
67
68 def unregister_teardown_callback(method_name_or_callback)
69 unregister_fixture_callback(:teardown, method_name_or_callback)
70 end
71
72 def before_setup_callbacks
73 collect_fixture_callbacks(:setup, :before)
74 end
75
76 def after_setup_callbacks
77 collect_fixture_callbacks(:setup, :after)
78 end
79
80 def before_cleanup_callbacks
81 collect_fixture_callbacks(:cleanup, :before)
82 end
83
84 def after_cleanup_callbacks
85 collect_fixture_callbacks(:cleanup, :after)
86 end
87
88 def before_teardown_callbacks
89 collect_fixture_callbacks(:teardown, :before)
90 end
91
92 def after_teardown_callbacks
93 collect_fixture_callbacks(:teardown, :after)
94 end
95
96 private
97 def register_fixture(fixture, *method_names, &callback)
98 options = {}
99 options = method_names.pop if method_names.last.is_a?(Hash)
100 callbacks = method_names
101 callbacks << callback if callback
102 attribute(fixture, options, *callbacks)
103 end
104
105 def unregister_fixture(fixture, *method_names_or_callbacks)
106 attribute(fixture, nil, *method_names_or_callbacks)
107 end
108
109 def valid_register_fixture_options?(options)
110 return true if options.empty?
111 return false if options.size > 1
112
113 key = options.keys.first
114 [:before, :after].include?(key) and
115 [:prepend, :append].include?(options[key])
116 end
117
118 def add_fixture_callback(how, variable_name, method_name_or_callback)
119 callbacks = instance_eval("#{variable_name} ||= []")
120
121 if how == :prepend
122 callbacks = [method_name_or_callback] | callbacks
123 else
124 callbacks = callbacks | [method_name_or_callback]
125 end
126 instance_variable_set(variable_name, callbacks)
127 end
128
129 def registered_callbacks_variable_name(fixture, order)
130 "@#{order}_#{fixture}_callbacks"
131 end
132
133 def unregistered_callbacks_variable_name(fixture)
134 "@unregistered_#{fixture}_callbacks"
135 end
136
137 def register_fixture_callback(fixture, method_name_or_callback, options,
138 default_order, default_how)
139 unless valid_register_fixture_options?(options)
140 message = "must be {:before => :prepend}, " +
141 "{:before => :append}, {:after => :prepend} or " +
142 "{:after => :append}: #{options.inspect}"
143 raise ArgumentError, message
144 end
145
146 if options.empty?
147 order, how = default_order, default_how
148 else
149 order, how = options.to_a.first
150 end
151 variable_name = registered_callbacks_variable_name(fixture, order)
152 add_fixture_callback(how, variable_name, method_name_or_callback)
153 end
154
155 def unregister_fixture_callback(fixture, method_name_or_callback)
156 variable_name = unregistered_callbacks_variable_name(fixture)
157 add_fixture_callback(:append, variable_name, method_name_or_callback)
158 end
159
160 def collect_fixture_callbacks(fixture, order)
161 callbacks_variable = registered_callbacks_variable_name(fixture, order)
162 unregistered_callbacks_variable =
163 unregistered_callbacks_variable_name(fixture)
164
165 base_index = ancestors.index(Fixture)
166 interested_ancestors = ancestors[0, base_index].reverse
167 interested_ancestors.inject([]) do |result, ancestor|
168 if ancestor.is_a?(Class)
169 ancestor.class_eval do
170 callbacks = instance_eval("#{callbacks_variable} ||= []")
171 unregistered_callbacks =
172 instance_eval("#{unregistered_callbacks_variable} ||= []")
173 (result | callbacks) - unregistered_callbacks
174 end
175 else
176 result
177 end
178 end
179 end
180 end
181
182 private
183 def run_fixture(fixture, options={})
184 [
185 self.class.send("before_#{fixture}_callbacks"),
186 fixture,
187 self.class.send("after_#{fixture}_callbacks")
188 ].flatten.each do |method_name_or_callback|
189 run_fixture_callback(method_name_or_callback, options)
190 end
191 end
192
193 def run_fixture_callback(method_name_or_callback, options)
194 if method_name_or_callback.respond_to?(:call)
195 callback = lambda do
196 instance_eval(&method_name_or_callback)
197 end
198 else
199 return unless respond_to?(method_name_or_callback, true)
200 callback = lambda do
201 send(method_name_or_callback)
202 end
203 end
204
205 begin
206 callback.call
207 rescue Exception
208 raise unless options[:handle_exception]
209 raise unless handle_exception($!)
210 end
211 end
212
213 def run_setup
214 run_fixture(:setup)
215 end
216
217 def run_cleanup
218 run_fixture(:cleanup)
219 end
220
221 def run_teardown
222 run_fixture(:teardown, :handle_exception => true)
223 end
224 end
225 end
226 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
-758
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/exceptionhandler'
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/testsuitecreator'
21 require 'test/unit/assertionfailederror'
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_methods = {}
112 def method_added(name) # :nodoc:
113 super
114 _added_methods = added_methods
115 stringified_name = name.to_s
116 if _added_methods.include?(stringified_name)
117 attribute(:redefined, {:backtrace => caller}, {}, stringified_name)
118 end
119 path, line, = caller[0].split(/:(\d+)/,2)
120 line = line.to_i if line
121 method_locations << {
122 :method_name => stringified_name,
123 :path => path,
124 :line => line,
125 }
126 _added_methods << stringified_name
127 end
128
129 def added_methods # :nodoc:
130 @@added_methods[self] ||= []
131 end
132
133 # Rolls up all of the test* methods in the fixture into
134 # one suite, creating a new instance of the fixture for
135 # each method.
136 def suite
137 suite_creator = TestSuiteCreator.new(self)
138 suite_creator.create
139 end
140
141 # Called before every test case runs. Can be used
142 # to set up fixture information used in test case
143 # scope.
144 #
145 # Here is an example test case:
146 # class TestMyClass < Test::Unit::TestCase
147 # class << self
148 # def startup
149 # ...
150 # end
151 # end
152 #
153 # def setup
154 # ...
155 # end
156 #
157 # def test_my_class1
158 # ...
159 # end
160 #
161 # def test_my_class2
162 # ...
163 # end
164 # end
165 #
166 # Here is a call order:
167 # * startup
168 # * setup
169 # * test_my_class1 (or test_my_class2)
170 # * setup
171 # * test_my_class2 (or test_my_class1)
172 #
173 # Note that you should not assume test order. Tests
174 # should be worked in any order.
175 def startup
176 end
177
178 # Called after every test case runs. Can be used to tear
179 # down fixture information used in test case scope.
180 #
181 # Here is an example test case:
182 # class TestMyClass < Test::Unit::TestCase
183 # class << self
184 # def shutdown
185 # ...
186 # end
187 # end
188 #
189 # def teardown
190 # ...
191 # end
192 #
193 # def test_my_class1
194 # ...
195 # end
196 #
197 # def test_my_class2
198 # ...
199 # end
200 # end
201 #
202 # Here is a call order:
203 # * test_my_class1 (or test_my_class2)
204 # * teardown
205 # * test_my_class2 (or test_my_class1)
206 # * teardown
207 # * shutdown
208 #
209 # Note that you should not assume test order. Tests
210 # should be worked in any order.
211 def shutdown
212 end
213
214 @@test_orders = {}
215
216 # Returns the current test order. This returns
217 # +:alphabetic+ by default.
218 def test_order
219 @@test_orders[self] || AVAILABLE_ORDERS.first
220 end
221
222 # Sets the current test order.
223 #
224 # Here are the available _order_:
225 # [:alphabetic]
226 # Default. Tests are sorted in alphabetic order.
227 # [:random]
228 # Tests are sorted in random order.
229 # [:defined]
230 # Tests are sorted in defined order.
231 def test_order=(order)
232 @@test_orders[self] = order
233 end
234
235 # Defines a test in declarative syntax or marks
236 # following method as a test method.
237 #
238 # In declarative syntax usage, the following two
239 # test definitions are the almost same:
240 #
241 # description "register user"
242 # def test_register_user
243 # ...
244 # end
245 #
246 # test "register user" do
247 # ...
248 # end
249 #
250 # In test method mark usage, the "my_test_method" is
251 # treated as a test method:
252 #
253 # test
254 # def my_test_method
255 # assert_equal("call me", ...)
256 # end
257 def test(*test_description_or_targets, &block)
258 if block_given?
259 test_description = test_description_or_targets.first
260 if test_description.nil?
261 raise ArgumentError, "test description is missing"
262 end
263 n_arguments = test_description_or_targets.size
264 if n_arguments > 1
265 message = "wrong number of arguments (#{n_arguments} for 1)"
266 raise ArgumentError, message
267 end
268 method_name = "test: #{test_description}"
269 define_method(method_name, &block)
270 description(test_description, method_name)
271 attribute(:test, true, {}, method_name)
272 if block.respond_to?(:source_location)
273 attribute(:source_location, block.source_location, {}, method_name)
274 end
275 else
276 targets = test_description_or_targets
277 attribute(:test, true, {}, *targets)
278 end
279 end
280
281 # Describes a test.
282 #
283 # The following example associates "register a
284 # normal user" description with "test_register"
285 # test.
286 #
287 # description "register a normal user"
288 # def test_register
289 # ...
290 # end
291 def description(value, target=nil)
292 targets = [target].compact
293 attribute(:description, value, {}, *targets)
294 end
295
296 # Defines a sub test case.
297 #
298 # This is a syntax sugar. The both of the following codes are
299 # the same in meaning:
300 #
301 # Standard:
302 # class TestParent < Test::UnitTestCase
303 # class TestChild < self
304 # def test_in_child
305 # end
306 # end
307 # end
308 #
309 # Syntax sugar:
310 # class TestParent < Test::UnitTestCase
311 # sub_test_case("TestChild") do
312 # def test_in_child
313 # end
314 # end
315 # end
316 #
317 # The diffrence of them are the following:
318 #
319 # * Test case created by {sub_test_case} is an anonymous class.
320 # So you can't refer the test case by name.
321 # * The class name of class style must follow
322 # constant naming rule in Ruby. But the name of test case
323 # created by {sub_test_case} doesn't need to follow the rule.
324 # For example, you can use a space in name such as "child test".
325 #
326 # @param name [String] The name of newly created sub test case.
327 # @yield
328 # The block is evaludated under the newly created sub test
329 # case class context.
330 # @return [Test::Unit::TestCase] Created sub test case class.
331 def sub_test_case(name, &block)
332 sub_test_case = Class.new(self) do
333 singleton_class = class << self; self; end
334 singleton_class.send(:define_method, :name) do
335 name
336 end
337 end
338 sub_test_case.class_eval(&block)
339 sub_test_case
340 end
341
342 # Checkes whether a test that is mathched the query is
343 # defined.
344 #
345 # @option query [String] :path (nil)
346 # the path where a test is defined in.
347 # @option query [Numeric] :line (nil)
348 # the line number where a test is defined at.
349 # @option query [String] :method_name (nil)
350 # the method name for a test.
351 def test_defined?(query)
352 query_path = query[:path]
353 query_line = query[:line]
354 query_method_name = query[:method_name]
355
356 available_locations = method_locations
357 if query_path
358 available_locations = available_locations.find_all do |location|
359 location[:path].end_with?(query_path)
360 end
361 end
362 if query_line
363 available_location = available_locations.reverse.find do |location|
364 query_line >= location[:line]
365 end
366 return false if available_location.nil?
367 available_locations = [available_location]
368 end
369 if query_method_name
370 available_location = available_locations.find do |location|
371 query_method_name == location[:method_name]
372 end
373 return false if available_location.nil?
374 available_locations = [available_location]
375 end
376
377 not available_locations.empty?
378 end
379
380 private
381 # @private
382 @@method_locations = {}
383 # @private
384 def method_locations
385 @@method_locations[self] ||= []
386 end
387 end
388
389 attr_reader :method_name
390
391 # Creates a new instance of the fixture for running the
392 # test represented by test_method_name.
393 def initialize(test_method_name)
394 @method_name = test_method_name
395 @internal_data = InternalData.new
396 end
397
398 # Assigns test data to the test. It is used in internal.
399 def assign_test_data(label, data) # :nodoc:
400 @internal_data.assign_test_data(label, data)
401 end
402
403 # Returns the test is valid test. It is used in internal.
404 def valid? # :nodoc:
405 return false unless respond_to?(@method_name)
406 test_method = method(@method_name)
407 if @internal_data.have_test_data?
408 return false unless test_method.arity == 1
409 else
410 return false unless test_method.arity <= 0
411 end
412 owner = Util::MethodOwnerFinder.find(self, @method_name)
413 if owner.class != Module and self.class != owner
414 return false
415 end
416 true
417 end
418
419 # Runs the individual test method represented by this
420 # instance of the fixture, collecting statistics, failures
421 # and errors in result.
422 def run(result)
423 begin
424 @_result = result
425 @internal_data.test_started
426 yield(STARTED, name)
427 yield(STARTED_OBJECT, self)
428 begin
429 run_setup
430 run_test
431 run_cleanup
432 add_pass
433 rescue Exception
434 @internal_data.interrupted
435 raise unless handle_exception($!)
436 ensure
437 begin
438 run_teardown
439 rescue Exception
440 raise unless handle_exception($!)
441 end
442 end
443 @internal_data.test_finished
444 result.add_run
445 yield(FINISHED, name)
446 yield(FINISHED_OBJECT, self)
447 ensure
448 # @_result = nil # For test-spec's after_all :<
449 end
450 end
451
452 # Called before every test method runs. Can be used
453 # to set up fixture information.
454 #
455 # You can add additional setup tasks by the following
456 # code:
457 # class TestMyClass < Test::Unit::TestCase
458 # def setup
459 # ...
460 # end
461 #
462 # setup
463 # def my_setup1
464 # ...
465 # end
466 #
467 # setup do
468 # ... # setup callback1
469 # end
470 #
471 # setup
472 # def my_setup2
473 # ...
474 # end
475 #
476 # setup do
477 # ... # setup callback2
478 # end
479 #
480 # def test_my_class
481 # ...
482 # end
483 # end
484 #
485 # Here is a call order:
486 # * setup
487 # * my_setup1
488 # * setup callback1
489 # * my_setup2
490 # * setup callback2
491 # * test_my_class
492 def setup
493 end
494
495 # Called after every test method runs but the test
496 # method isn't marked as 'passed'. Can be used to
497 # clean up and/or verify tested condition.
498 # e.g. Can be used to verify mock.
499 #
500 # You can add additional cleanup tasks by the following
501 # code:
502 # class TestMyClass < Test::Unit::TestCase
503 # def cleanup
504 # ...
505 # end
506 #
507 # cleanup
508 # def my_cleanup1
509 # ...
510 # end
511 #
512 # cleanup do
513 # ... # cleanup callback1
514 # end
515 #
516 # cleanup
517 # def my_cleanup2
518 # ...
519 # end
520 #
521 # cleanup do
522 # ... # cleanup callback2
523 # end
524 #
525 # def test_my_class
526 # ...
527 # end
528 # end
529 #
530 # Here is a call order:
531 # * test_my_class
532 # * cleanup callback2
533 # * my_cleanup2
534 # * cleanup callback1
535 # * my_cleanup1
536 # * cleanup
537 def cleanup
538 end
539
540 # Called after every test method runs. Can be used to tear
541 # down fixture information.
542 #
543 # You can add additional teardown tasks by the following
544 # code:
545 # class TestMyClass < Test::Unit::TestCase
546 # def teardown
547 # ...
548 # end
549 #
550 # teardown
551 # def my_teardown1
552 # ...
553 # end
554 #
555 # teardown do
556 # ... # teardown callback1
557 # end
558 #
559 # teardown
560 # def my_teardown2
561 # ...
562 # end
563 #
564 # teardown do
565 # ... # teardown callback2
566 # end
567 #
568 # def test_my_class
569 # ...
570 # end
571 # end
572 #
573 # Here is a call order:
574 # * test_my_class
575 # * teardown callback2
576 # * my_teardown2
577 # * teardown callback1
578 # * my_teardown1
579 # * teardown
580 def teardown
581 end
582
583 def default_test
584 flunk("No tests were specified")
585 end
586
587 def size
588 1
589 end
590
591 # Returns a label of test data for the test. If the
592 # test isn't associated with any test data, it returns
593 # +nil+.
594 def data_label
595 @internal_data.test_data_label
596 end
597
598 # Returns a human-readable name for the specific test that
599 # this instance of TestCase represents.
600 def name
601 if @internal_data.have_test_data?
602 "#{@method_name}[#{data_label}](#{self.class.name})"
603 else
604 "#{@method_name}(#{self.class.name})"
605 end
606 end
607
608 # Returns a description for the test. A description
609 # will be associated by Test::Unit::TestCase.test or
610 # Test::Unit::TestCase.description.
611 #
612 # Returns a name for the test for no description test.
613 def description
614 self[:description] || name
615 end
616
617 # Overridden to return #name.
618 def to_s
619 name
620 end
621
622 # It's handy to be able to compare TestCase instances.
623 def ==(other)
624 return false unless other.kind_of?(self.class)
625 return false unless @method_name == other.method_name
626 return false unless data_label == other.data_label
627 self.class == other.class
628 end
629
630 # Returns a Time at the test was started.
631 def start_time
632 @internal_data.start_time
633 end
634
635 # Returns elapsed time for the test was ran.
636 def elapsed_time
637 @internal_data.elapsed_time
638 end
639
640 # Returns whether the test is interrupted.
641 def interrupted?
642 @internal_data.interrupted?
643 end
644
645 # Returns whether this individual test passed or
646 # not. Primarily for use in teardown so that artifacts
647 # can be left behind if the test fails.
648 def passed?
649 @internal_data.passed?
650 end
651
652 # Notify that a problem is occurred in the test. It means that
653 # the test is a failed test. If any failed tests exist in test
654 # suites, the test process exits with failure exit status.
655 #
656 # This is a public API for developers who extend test-unit.
657 #
658 # @return [void]
659 def problem_occurred
660 @internal_data.problem_occurred
661 end
662
663 # Notify that the test is passed. Normally, it is not needed
664 # because #run calls it automatically. If you want to override
665 # #run, it is not a good idea. Please contact test-unit
666 # developers. We will help you without your custom #run. For
667 # example, we may add a new hook in #run.
668 #
669 # This is a public API for developers who extend test-unit.
670 #
671 # @return [void]
672 def add_pass
673 current_result.add_pass
674 end
675
676 private
677 def current_result
678 @_result
679 end
680
681 def run_test
682 redefined_info = self[:redefined]
683 if redefined_info
684 notify("#{self.class}\##{@method_name} was redefined",
685 :backtrace => redefined_info[:backtrace])
686 end
687 if @internal_data.have_test_data?
688 __send__(@method_name, @internal_data.test_data)
689 else
690 __send__(@method_name)
691 end
692 end
693
694 def handle_exception(exception)
695 self.class.exception_handlers.each do |handler|
696 if handler.respond_to?(:call)
697 handled = handler.call(self, exception)
698 else
699 handled = send(handler, exception)
700 end
701 return true if handled
702 end
703 false
704 end
705
706 def add_assertion
707 current_result.add_assertion
708 end
709
710 class InternalData
711 attr_reader :start_time, :elapsed_time
712 attr_reader :test_data_label, :test_data
713 def initialize
714 @start_time = nil
715 @elapsed_time = nil
716 @passed = true
717 @interrupted = false
718 @test_data_label = nil
719 @test_data = nil
720 end
721
722 def passed?
723 @passed
724 end
725
726 def interrupted?
727 @interrupted
728 end
729
730 def assign_test_data(label, data)
731 @test_data_label = label
732 @test_data = data
733 end
734
735 def have_test_data?
736 not @test_data_label.nil?
737 end
738
739 def test_started
740 @start_time = Time.now
741 end
742
743 def test_finished
744 @elapsed_time = Time.now - @start_time
745 end
746
747 def problem_occurred
748 @passed = false
749 end
750
751 def interrupted
752 @interrupted = true
753 end
754 end
755 end
756 end
757 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
-79
vendor/test/unit/testsuitecreator.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.attributes(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 method_names = @test_case.public_instance_methods(true).collect do |name|
44 name.to_s
45 end
46 test_names = method_names.find_all do |method_name|
47 method_name =~ /^test./ or @test_case.attributes(method_name)[:test]
48 end
49 send("sort_test_names_in_#{@test_case.test_order}_order", test_names)
50 end
51
52 def sort_test_names_in_alphabetic_order(test_names)
53 test_names.sort
54 end
55
56 def sort_test_names_in_random_order(test_names)
57 test_names.sort_by {rand(test_names.size)}
58 end
59
60 def sort_test_names_in_defined_order(test_names)
61 added_methods = @test_case.added_methods
62 test_names.sort do |test1, test2|
63 test1_defined_order = added_methods.index(test1)
64 test2_defined_order = added_methods.index(test2)
65 if test1_defined_order and test2_defined_order
66 test1_defined_order <=> test2_defined_order
67 elsif test1_defined_order
68 1
69 elsif test2_defined_order
70 -1
71 else
72 test1 <=> test2
73 end
74 end
75 end
76 end
77 end
78 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
-627
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 if fault.message.include?("\n")
187 output(":")
188 output_fault_message(fault)
189 else
190 output(": #{fault.message}")
191 end
192 output(fault.test_name)
193 end
194 output_fault_backtrace(fault)
195 end
196 end
197
198 def output_fault_message(fault)
199 fault.message.each_line do |line|
200 output(" #{line.chomp}")
201 end
202 end
203
204 def output_fault_backtrace(fault)
205 snippet_is_shown = false
206 detector = FaultLocationDetector.new(fault, @code_snippet_fetcher)
207 backtrace = fault.location
208 # workaround for test-spec. :<
209 # see also GitHub:#22
210 backtrace ||= []
211 backtrace.each_with_index do |entry, i|
212 output(entry)
213 next if snippet_is_shown
214 next unless detector.target?(entry)
215 file, line_number, = detector.split_backtrace_entry(entry)
216 snippet_is_shown = output_code_snippet(file, line_number,
217 fault_color(fault))
218 end
219 end
220
221 def output_code_snippet(file, line_number, target_line_color=nil)
222 lines = @code_snippet_fetcher.fetch(file, line_number)
223 return false if lines.empty?
224
225 max_n = lines.collect {|n, line, attributes| n}.max
226 digits = (Math.log10(max_n) + 1).truncate
227 lines.each do |n, line, attributes|
228 if attributes[:target_line?]
229 line_color = target_line_color
230 current_line_mark = "=>"
231 else
232 line_color = nil
233 current_line_mark = ""
234 end
235 output(" %2s %*d: %s" % [current_line_mark, digits, n, line],
236 line_color)
237 end
238 true
239 end
240
241 def output_failure_message(failure)
242 if failure.expected.respond_to?(:encoding) and
243 failure.actual.respond_to?(:encoding) and
244 failure.expected.encoding != failure.actual.encoding
245 need_encoding = true
246 else
247 need_encoding = false
248 end
249 output(failure.user_message) if failure.user_message
250 output_single("<")
251 output_single(failure.inspected_expected, color("pass"))
252 output_single(">")
253 if need_encoding
254 output_single("(")
255 output_single(failure.expected.encoding.name, color("pass"))
256 output_single(")")
257 end
258 output(" expected but was")
259 output_single("<")
260 output_single(failure.inspected_actual, color("failure"))
261 output_single(">")
262 if need_encoding
263 output_single("(")
264 output_single(failure.actual.encoding.name, color("failure"))
265 output_single(")")
266 end
267 output("")
268 from, to = prepare_for_diff(failure.expected, failure.actual)
269 if from and to
270 if need_encoding
271 unless from.valid_encoding?
272 from = from.dup.force_encoding("ASCII-8BIT")
273 end
274 unless to.valid_encoding?
275 to = to.dup.force_encoding("ASCII-8BIT")
276 end
277 end
278 from_lines = from.split(/\r?\n/)
279 to_lines = to.split(/\r?\n/)
280 if need_encoding
281 from_lines << ""
282 to_lines << ""
283 from_lines << "Encoding: #{failure.expected.encoding.name}"
284 to_lines << "Encoding: #{failure.actual.encoding.name}"
285 end
286 differ = ColorizedReadableDiffer.new(from_lines, to_lines, self)
287 if differ.need_diff?
288 output("")
289 output("diff:")
290 differ.diff
291 end
292 end
293 end
294
295 def output_fault_in_short(fault)
296 output_single(fault.message, fault_color(fault))
297 output(" [#{fault.test_name}]")
298 output(fault.location.first)
299 end
300
301 def format_fault(fault)
302 fault.long_display
303 end
304
305 def output_statistics(elapsed_time)
306 output("Finished in #{elapsed_time} seconds.")
307 nl
308 output(@result, result_color)
309 output("%g%% passed" % @result.pass_percentage, result_color)
310 unless elapsed_time.zero?
311 nl
312 test_throughput = @result.run_count / elapsed_time
313 assertion_throughput = @result.assertion_count / elapsed_time
314 throughput = [
315 "%.2f tests/s" % test_throughput,
316 "%.2f assertions/s" % assertion_throughput,
317 ]
318 output(throughput.join(", "))
319 end
320 end
321
322 def test_started(test)
323 return unless output?(VERBOSE)
324
325 name = test.name.sub(/\(.+?\)\z/, '')
326 right_space = 8 * 2
327 left_space = @progress_row_max - right_space
328 left_space = left_space - indent.size - name.size
329 tab_stop = "\t" * ([left_space - 1, 0].max / 8)
330 output_single("#{indent}#{name}:#{tab_stop}", nil, VERBOSE)
331 @test_start = Time.now
332 end
333
334 def test_finished(test)
335 unless @already_outputted
336 output_progress(".", color("pass"))
337 end
338 @already_outputted = false
339
340 return unless output?(VERBOSE)
341
342 output(": (%f)" % (Time.now - @test_start), nil, VERBOSE)
343 end
344
345 def suite_name(prefix, suite)
346 name = suite.name
347 if name.nil?
348 "(anonymous)"
349 else
350 name.sub(/\A#{Regexp.escape(prefix)}/, "")
351 end
352 end
353
354 def test_suite_started(suite)
355 last_test_suite = @test_suites.last
356 @test_suites << suite
357 if @top_level
358 @top_level = false
359 return
360 end
361
362 output_single(indent, nil, VERBOSE)
363 if suite.test_case.nil?
364 _color = color("suite")
365 else
366 _color = color("case")
367 end
368 prefix = "#{last_test_suite.name}::"
369 output_single(suite_name(prefix, suite), _color, VERBOSE)
370 output(": ", nil, VERBOSE)
371 @indent += 2
372 end
373
374 def test_suite_finished(suite)
375 @indent -= 2
376 @test_suites.pop
377 end
378
379 def indent
380 if output?(VERBOSE)
381 " " * @indent
382 else
383 ""
384 end
385 end
386
387 def nl(level=nil)
388 output("", nil, level)
389 end
390
391 def output(something, color=nil, level=nil)
392 return unless output?(level)
393 output_single(something, color, level)
394 @output.puts
395 end
396
397 def output_single(something, color=nil, level=nil)
398 return false unless output?(level)
399 if @use_color and color
400 something = "%s%s%s" % [color.escape_sequence,
401 something,
402 @reset_color.escape_sequence]
403 end
404 @output.write(something)
405 @output.flush
406 true
407 end
408
409 def output_progress(mark, color=nil)
410 if output_single(mark, color, PROGRESS_ONLY)
411 return unless @progress_row_max > 0
412 @progress_row += mark.size
413 if @progress_row >= @progress_row_max
414 nl unless @output_level == VERBOSE
415 @progress_row = 0
416 end
417 end
418 end
419
420 def output_progress_in_detail_marker(fault)
421 if @progress_row_max > 0
422 output("=" * @progress_row_max, fault_color(fault))
423 else
424 nl
425 end
426 end
427
428 def output_progress_in_detail(fault)
429 return if @output_level == SILENT
430 nl
431 output_progress_in_detail_marker(fault)
432 if categorize_fault(fault) == :need_detail_faults
433 output_fault_in_detail(fault)
434 else
435 output_fault_in_short(fault)
436 end
437 output_progress_in_detail_marker(fault)
438 @progress_row = 0
439 end
440
441 def output?(level)
442 (level || @current_output_level) <= @output_level
443 end
444
445 def color(name)
446 _color = @color_scheme[name]
447 _color ||= @color_scheme["success"] if name == "pass"
448 _color ||= ColorScheme.default[name]
449 _color
450 end
451
452 def fault_color(fault)
453 fault_class_color(fault.class)
454 end
455
456 def fault_class_color(fault_class)
457 color(fault_class.name.split(/::/).last.downcase)
458 end
459
460 def result_color
461 color(@result.status)
462 end
463
464 def guess_color_availability
465 return false unless @output.tty?
466 case ENV["TERM"]
467 when /(?:term|screen)(?:-(?:256)?color)?\z/
468 true
469 else
470 return true if ENV["EMACS"] == "t"
471 false
472 end
473 end
474
475 def guess_progress_row_max
476 term_width = guess_term_width
477 if term_width.zero?
478 if ENV["EMACS"] == "t"
479 -1
480 else
481 79
482 end
483 else
484 term_width
485 end
486 end
487
488 def guess_term_width
489 Integer(ENV["COLUMNS"] || ENV["TERM_WIDTH"] || 0)
490 rescue ArgumentError
491 0
492 end
493 end
494
495 class ColorizedReadableDiffer < Diff::ReadableDiffer
496 def initialize(from, to, runner)
497 @runner = runner
498 super(from, to)
499 end
500
501 def need_diff?(options={})
502 operations.each do |tag,|
503 return true if [:replace, :equal].include?(tag)
504 end
505 false
506 end
507
508 private
509 def output_single(something, color=nil)
510 @runner.send(:output_single, something, color)
511 end
512
513 def output(something, color=nil)
514 @runner.send(:output, something, color)
515 end
516
517 def color(name)
518 @runner.send(:color, name)
519 end
520
521 def cut_off_ratio
522 0
523 end
524
525 def default_ratio
526 0
527 end
528
529 def tag(mark, color_name, contents)
530 _color = color(color_name)
531 contents.each do |content|
532 output_single(mark, _color)
533 output_single(" ")
534 output(content)
535 end
536 end
537
538 def tag_deleted(contents)
539 tag("-", "diff-deleted-tag", contents)
540 end
541
542 def tag_inserted(contents)
543 tag("+", "diff-inserted-tag", contents)
544 end
545
546 def tag_equal(contents)
547 tag(" ", "normal", contents)
548 end
549
550 def tag_difference(contents)
551 tag("?", "diff-difference-tag", contents)
552 end
553
554 def diff_line(from_line, to_line)
555 to_operations = []
556 from_line, to_line, _operations = line_operations(from_line, to_line)
557
558 no_replace = true
559 _operations.each do |tag,|
560 if tag == :replace
561 no_replace = false
562 break
563 end
564 end
565
566 output_single("?", color("diff-difference-tag"))
567 output_single(" ")
568 _operations.each do |tag, from_start, from_end, to_start, to_end|
569 from_width = compute_width(from_line, from_start, from_end)
570 to_width = compute_width(to_line, to_start, to_end)
571 case tag
572 when :replace
573 output_single(from_line[from_start...from_end],
574 color("diff-deleted"))
575 if (from_width < to_width)
576 output_single(" " * (to_width - from_width))
577 end
578 to_operations << Proc.new do
579 output_single(to_line[to_start...to_end],
580 color("diff-inserted"))
581 if (to_width < from_width)
582 output_single(" " * (from_width - to_width))
583 end
584 end
585 when :delete
586 output_single(from_line[from_start...from_end],
587 color("diff-deleted"))
588 unless no_replace
589 to_operations << Proc.new {output_single(" " * from_width)}
590 end
591 when :insert
592 if no_replace
593 output_single(to_line[to_start...to_end],
594 color("diff-inserted"))
595 else
596 output_single(" " * to_width)
597 to_operations << Proc.new do
598 output_single(to_line[to_start...to_end],
599 color("diff-inserted"))
600 end
601 end
602 when :equal
603 output_single(from_line[from_start...from_end])
604 unless no_replace
605 to_operations << Proc.new {output_single(" " * to_width)}
606 end
607 else
608 raise "unknown tag: #{tag}"
609 end
610 end
611 output("")
612
613 unless to_operations.empty?
614 output_single("?", color("diff-difference-tag"))
615 output_single(" ")
616 to_operations.each do |operation|
617 operation.call
618 end
619 output("")
620 end
621 end
622 end
623 end
624 end
625 end
626 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
-53
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 jruby_internal_p = lambda do |entry|
26 entry.start_with?("org/jruby/")
27 end
28
29 found_prefix = false
30 new_backtrace = backtrace.reverse.reject do |entry|
31 if test_unit_internal_p.call(entry)
32 found_prefix = true
33 true
34 elsif found_prefix
35 jruby_internal_p.call(entry)
36 else
37 true
38 end
39 end.reverse
40
41 if new_backtrace.empty?
42 new_backtrace = backtrace.reject do |entry|
43 test_unit_internal_p.call(entry) or jruby_internal_p.call(entry)
44 end
45 new_backtrace = backtrace if new_backtrace.empty?
46 end
47 new_backtrace
48 end
49 end
50 end
51 end
52 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 = '2.5.5'
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 (twoggle@gmail.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
-70
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 #
19 # In Ruby 1.8, +str1+ and +str2+ should be ASCII, UTF-8, or a one-byte-per
20 # character encoding such as ISO-8859-*. They will be treated as UTF-8 if
21 # $KCODE is set appropriately (i.e. 'u'). Otherwise, the comparison will be
22 # performed byte-by-byte. There is no specific support for Shift-JIS or EUC
23 # strings.
24 #
25 # In Ruby 1.9+, the strings will be processed as UTF-8.
26 #
27 # When using Unicode text, be aware that this algorithm does not perform
28 # normalisation. If there is a possibility of different normalised forms
29 # being used, normalisation should be performed beforehand.
30 #
31 def distance(str1, str2)
32 prepare =
33 if "ruby".respond_to?(:encoding)
34 lambda { |str| str.encode(Encoding::UTF_8).unpack("U*") }
35 else
36 rule = $KCODE.match(/^U/i) ? "U*" : "C*"
37 lambda { |str| str.unpack(rule) }
38 end
39
40 s, t = [str1, str2].map(&prepare)
41 n = s.length
42 m = t.length
43 return m if n.zero?
44 return n if m.zero?
45
46 d = (0..m).to_a
47 x = nil
48
49 n.times do |i|
50 e = i + 1
51 m.times do |j|
52 cost = (s[i] == t[j]) ? 0 : 1
53 x = [
54 d[j+1] + 1, # insertion
55 e + 1, # deletion
56 d[j] + cost # substitution
57 ].min
58 d[j] = e
59 e = x
60 end
61 d[m] = x
62 end
63
64 return x
65 end
66
67 extend self
68 end
69 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 [ /[cq]/, '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
-61
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 return nil if str.empty?
30
31 str = str.upcase
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 == nil then
43 return nil
44 elsif code != last_code then
45 soundex_code += code
46 last_code = code
47 end
48 end # for
49
50 return soundex_code + "000"[0,4-soundex_code.size]
51 end
52 module_function :soundex_str
53
54 def get_code(char)
55 char.tr! "AEIOUYWHBPFVCSKGJQXZDTLMNR", "00000000111122222222334556"
56 end
57 module_function :get_code
58
59 end # module Soundex
60 end # module Text
+0
-9
vendor/text/version.rb less more
0 module Text
1 module VERSION #:nodoc:
2 MAJOR = 1
3 MINOR = 2
4 TINY = 3
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