Codebase list mikutter / upstream/3.9.4+dfsg
New upstream version 3.9.4+dfsg HIGUCHI Daisuke (VDR dai) 4 years ago
215 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
-61
vendor/addressable/idna/native.rb less more
0 # frozen_string_literal: true
1
2 # encoding:utf-8
3 #--
4 # Copyright (C) Bob Aman
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #++
18
19
20 require "idn"
21
22 module Addressable
23 module IDNA
24 def self.punycode_encode(value)
25 IDN::Punycode.encode(value.to_s)
26 end
27
28 def self.punycode_decode(value)
29 IDN::Punycode.decode(value.to_s)
30 end
31
32 def self.unicode_normalize_kc(value)
33 IDN::Stringprep.nfkc_normalize(value.to_s)
34 end
35
36 def self.to_ascii(value)
37 value.to_s.split('.', -1).map do |segment|
38 if segment.size > 0 && segment.size < 64
39 IDN::Idna.toASCII(segment, IDN::Idna::ALLOW_UNASSIGNED)
40 elsif segment.size >= 64
41 segment
42 else
43 ''
44 end
45 end.join('.')
46 end
47
48 def self.to_unicode(value)
49 value.to_s.split('.', -1).map do |segment|
50 if segment.size > 0 && segment.size < 64
51 IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED)
52 elsif segment.size >= 64
53 segment
54 else
55 ''
56 end
57 end.join('.')
58 end
59 end
60 end
+0
-676
vendor/addressable/idna/pure.rb less more
0 # frozen_string_literal: true
1
2 # encoding:utf-8
3 #--
4 # Copyright (C) Bob Aman
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #++
18
19
20 module Addressable
21 module IDNA
22 # This module is loosely based on idn_actionmailer by Mick Staugaard,
23 # the unicode library by Yoshida Masato, and the punycode implementation
24 # by Kazuhiro Nishiyama. Most of the code was copied verbatim, but
25 # some reformatting was done, and some translation from C was done.
26 #
27 # Without their code to work from as a base, we'd all still be relying
28 # on the presence of libidn. Which nobody ever seems to have installed.
29 #
30 # Original sources:
31 # http://github.com/staugaard/idn_actionmailer
32 # http://www.yoshidam.net/Ruby.html#unicode
33 # http://rubyforge.org/frs/?group_id=2550
34
35
36 UNICODE_TABLE = File.expand_path(
37 File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data')
38 )
39
40 ACE_PREFIX = "xn--"
41
42 UTF8_REGEX = /\A(?:
43 [\x09\x0A\x0D\x20-\x7E] # ASCII
44 | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
45 | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
46 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
47 | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
48 | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
49 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
50 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
51 )*\z/mnx
52
53 UTF8_REGEX_MULTIBYTE = /(?:
54 [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
55 | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
56 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
57 | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
58 | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
59 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
60 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
61 )/mnx
62
63 # :startdoc:
64
65 # Converts from a Unicode internationalized domain name to an ASCII
66 # domain name as described in RFC 3490.
67 def self.to_ascii(input)
68 input = input.to_s unless input.is_a?(String)
69 input = input.dup
70 if input.respond_to?(:force_encoding)
71 input.force_encoding(Encoding::ASCII_8BIT)
72 end
73 if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE
74 parts = unicode_downcase(input).split('.')
75 parts.map! do |part|
76 if part.respond_to?(:force_encoding)
77 part.force_encoding(Encoding::ASCII_8BIT)
78 end
79 if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
80 ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
81 else
82 part
83 end
84 end
85 parts.join('.')
86 else
87 input
88 end
89 end
90
91 # Converts from an ASCII domain name to a Unicode internationalized
92 # domain name as described in RFC 3490.
93 def self.to_unicode(input)
94 input = input.to_s unless input.is_a?(String)
95 parts = input.split('.')
96 parts.map! do |part|
97 if part =~ /^#{ACE_PREFIX}(.+)/
98 begin
99 punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
100 rescue Addressable::IDNA::PunycodeBadInput
101 # toUnicode is explicitly defined as never-fails by the spec
102 part
103 end
104 else
105 part
106 end
107 end
108 output = parts.join('.')
109 if output.respond_to?(:force_encoding)
110 output.force_encoding(Encoding::UTF_8)
111 end
112 output
113 end
114
115 # Unicode normalization form KC.
116 def self.unicode_normalize_kc(input)
117 input = input.to_s unless input.is_a?(String)
118 unpacked = input.unpack("U*")
119 unpacked =
120 unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
121 return unpacked.pack("U*")
122 end
123
124 ##
125 # Unicode aware downcase method.
126 #
127 # @api private
128 # @param [String] input
129 # The input string.
130 # @return [String] The downcased result.
131 def self.unicode_downcase(input)
132 input = input.to_s unless input.is_a?(String)
133 unpacked = input.unpack("U*")
134 unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
135 return unpacked.pack("U*")
136 end
137 (class <<self; private :unicode_downcase; end)
138
139 def self.unicode_compose(unpacked)
140 unpacked_result = []
141 length = unpacked.length
142
143 return unpacked if length == 0
144
145 starter = unpacked[0]
146 starter_cc = lookup_unicode_combining_class(starter)
147 starter_cc = 256 if starter_cc != 0
148 for i in 1...length
149 ch = unpacked[i]
150
151 if (starter_cc == 0 &&
152 (composite = unicode_compose_pair(starter, ch)) != nil)
153 starter = composite
154 else
155 unpacked_result << starter
156 starter = ch
157 end
158 end
159 unpacked_result << starter
160 return unpacked_result
161 end
162 (class <<self; private :unicode_compose; end)
163
164 def self.unicode_compose_pair(ch_one, ch_two)
165 if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
166 ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
167 # Hangul L + V
168 return HANGUL_SBASE + (
169 (ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
170 ) * HANGUL_TCOUNT
171 elsif ch_one >= HANGUL_SBASE &&
172 ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
173 (ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
174 ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
175 # Hangul LV + T
176 return ch_one + (ch_two - HANGUL_TBASE)
177 end
178
179 p = []
180 ucs4_to_utf8 = lambda do |ch|
181 if ch < 128
182 p << ch
183 elsif ch < 2048
184 p << (ch >> 6 | 192)
185 p << (ch & 63 | 128)
186 elsif ch < 0x10000
187 p << (ch >> 12 | 224)
188 p << (ch >> 6 & 63 | 128)
189 p << (ch & 63 | 128)
190 elsif ch < 0x200000
191 p << (ch >> 18 | 240)
192 p << (ch >> 12 & 63 | 128)
193 p << (ch >> 6 & 63 | 128)
194 p << (ch & 63 | 128)
195 elsif ch < 0x4000000
196 p << (ch >> 24 | 248)
197 p << (ch >> 18 & 63 | 128)
198 p << (ch >> 12 & 63 | 128)
199 p << (ch >> 6 & 63 | 128)
200 p << (ch & 63 | 128)
201 elsif ch < 0x80000000
202 p << (ch >> 30 | 252)
203 p << (ch >> 24 & 63 | 128)
204 p << (ch >> 18 & 63 | 128)
205 p << (ch >> 12 & 63 | 128)
206 p << (ch >> 6 & 63 | 128)
207 p << (ch & 63 | 128)
208 end
209 end
210
211 ucs4_to_utf8.call(ch_one)
212 ucs4_to_utf8.call(ch_two)
213
214 return lookup_unicode_composition(p)
215 end
216 (class <<self; private :unicode_compose_pair; end)
217
218 def self.unicode_sort_canonical(unpacked)
219 unpacked = unpacked.dup
220 i = 1
221 length = unpacked.length
222
223 return unpacked if length < 2
224
225 while i < length
226 last = unpacked[i-1]
227 ch = unpacked[i]
228 last_cc = lookup_unicode_combining_class(last)
229 cc = lookup_unicode_combining_class(ch)
230 if cc != 0 && last_cc != 0 && last_cc > cc
231 unpacked[i] = last
232 unpacked[i-1] = ch
233 i -= 1 if i > 1
234 else
235 i += 1
236 end
237 end
238 return unpacked
239 end
240 (class <<self; private :unicode_sort_canonical; end)
241
242 def self.unicode_decompose(unpacked)
243 unpacked_result = []
244 for cp in unpacked
245 if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
246 l, v, t = unicode_decompose_hangul(cp)
247 unpacked_result << l
248 unpacked_result << v if v
249 unpacked_result << t if t
250 else
251 dc = lookup_unicode_compatibility(cp)
252 unless dc
253 unpacked_result << cp
254 else
255 unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
256 end
257 end
258 end
259 return unpacked_result
260 end
261 (class <<self; private :unicode_decompose; end)
262
263 def self.unicode_decompose_hangul(codepoint)
264 sindex = codepoint - HANGUL_SBASE;
265 if sindex < 0 || sindex >= HANGUL_SCOUNT
266 l = codepoint
267 v = t = nil
268 return l, v, t
269 end
270 l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
271 v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
272 t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
273 if t == HANGUL_TBASE
274 t = nil
275 end
276 return l, v, t
277 end
278 (class <<self; private :unicode_decompose_hangul; end)
279
280 def self.lookup_unicode_combining_class(codepoint)
281 codepoint_data = UNICODE_DATA[codepoint]
282 (codepoint_data ?
283 (codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
284 0)
285 end
286 (class <<self; private :lookup_unicode_combining_class; end)
287
288 def self.lookup_unicode_compatibility(codepoint)
289 codepoint_data = UNICODE_DATA[codepoint]
290 (codepoint_data ?
291 codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
292 end
293 (class <<self; private :lookup_unicode_compatibility; end)
294
295 def self.lookup_unicode_lowercase(codepoint)
296 codepoint_data = UNICODE_DATA[codepoint]
297 (codepoint_data ?
298 (codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) :
299 codepoint)
300 end
301 (class <<self; private :lookup_unicode_lowercase; end)
302
303 def self.lookup_unicode_composition(unpacked)
304 return COMPOSITION_TABLE[unpacked]
305 end
306 (class <<self; private :lookup_unicode_composition; end)
307
308 HANGUL_SBASE = 0xac00
309 HANGUL_LBASE = 0x1100
310 HANGUL_LCOUNT = 19
311 HANGUL_VBASE = 0x1161
312 HANGUL_VCOUNT = 21
313 HANGUL_TBASE = 0x11a7
314 HANGUL_TCOUNT = 28
315 HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
316 HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
317
318 UNICODE_DATA_COMBINING_CLASS = 0
319 UNICODE_DATA_EXCLUSION = 1
320 UNICODE_DATA_CANONICAL = 2
321 UNICODE_DATA_COMPATIBILITY = 3
322 UNICODE_DATA_UPPERCASE = 4
323 UNICODE_DATA_LOWERCASE = 5
324 UNICODE_DATA_TITLECASE = 6
325
326 begin
327 if defined?(FakeFS)
328 fakefs_state = FakeFS.activated?
329 FakeFS.deactivate!
330 end
331 # This is a sparse Unicode table. Codepoints without entries are
332 # assumed to have the value: [0, 0, nil, nil, nil, nil, nil]
333 UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file|
334 Marshal.load(file.read)
335 end
336 ensure
337 if defined?(FakeFS)
338 FakeFS.activate! if fakefs_state
339 end
340 end
341
342 COMPOSITION_TABLE = {}
343 for codepoint, data in UNICODE_DATA
344 canonical = data[UNICODE_DATA_CANONICAL]
345 exclusion = data[UNICODE_DATA_EXCLUSION]
346
347 if canonical && exclusion == 0
348 COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint
349 end
350 end
351
352 UNICODE_MAX_LENGTH = 256
353 ACE_MAX_LENGTH = 256
354
355 PUNYCODE_BASE = 36
356 PUNYCODE_TMIN = 1
357 PUNYCODE_TMAX = 26
358 PUNYCODE_SKEW = 38
359 PUNYCODE_DAMP = 700
360 PUNYCODE_INITIAL_BIAS = 72
361 PUNYCODE_INITIAL_N = 0x80
362 PUNYCODE_DELIMITER = 0x2D
363
364 PUNYCODE_MAXINT = 1 << 64
365
366 PUNYCODE_PRINT_ASCII =
367 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
368 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
369 " !\"\#$%&'()*+,-./" +
370 "0123456789:;<=>?" +
371 "@ABCDEFGHIJKLMNO" +
372 "PQRSTUVWXYZ[\\]^_" +
373 "`abcdefghijklmno" +
374 "pqrstuvwxyz{|}~\n"
375
376 # Input is invalid.
377 class PunycodeBadInput < StandardError; end
378 # Output would exceed the space provided.
379 class PunycodeBigOutput < StandardError; end
380 # Input needs wider integers to process.
381 class PunycodeOverflow < StandardError; end
382
383 def self.punycode_encode(unicode)
384 unicode = unicode.to_s unless unicode.is_a?(String)
385 input = unicode.unpack("U*")
386 output = [0] * (ACE_MAX_LENGTH + 1)
387 input_length = input.size
388 output_length = [ACE_MAX_LENGTH]
389
390 # Initialize the state
391 n = PUNYCODE_INITIAL_N
392 delta = out = 0
393 max_out = output_length[0]
394 bias = PUNYCODE_INITIAL_BIAS
395
396 # Handle the basic code points:
397 input_length.times do |j|
398 if punycode_basic?(input[j])
399 if max_out - out < 2
400 raise PunycodeBigOutput,
401 "Output would exceed the space provided."
402 end
403 output[out] = input[j]
404 out += 1
405 end
406 end
407
408 h = b = out
409
410 # h is the number of code points that have been handled, b is the
411 # number of basic code points, and out is the number of characters
412 # that have been output.
413
414 if b > 0
415 output[out] = PUNYCODE_DELIMITER
416 out += 1
417 end
418
419 # Main encoding loop:
420
421 while h < input_length
422 # All non-basic code points < n have been
423 # handled already. Find the next larger one:
424
425 m = PUNYCODE_MAXINT
426 input_length.times do |j|
427 m = input[j] if (n...m) === input[j]
428 end
429
430 # Increase delta enough to advance the decoder's
431 # <n,i> state to <m,0>, but guard against overflow:
432
433 if m - n > (PUNYCODE_MAXINT - delta) / (h + 1)
434 raise PunycodeOverflow, "Input needs wider integers to process."
435 end
436 delta += (m - n) * (h + 1)
437 n = m
438
439 input_length.times do |j|
440 # Punycode does not need to check whether input[j] is basic:
441 if input[j] < n
442 delta += 1
443 if delta == 0
444 raise PunycodeOverflow,
445 "Input needs wider integers to process."
446 end
447 end
448
449 if input[j] == n
450 # Represent delta as a generalized variable-length integer:
451
452 q = delta; k = PUNYCODE_BASE
453 while true
454 if out >= max_out
455 raise PunycodeBigOutput,
456 "Output would exceed the space provided."
457 end
458 t = (
459 if k <= bias
460 PUNYCODE_TMIN
461 elsif k >= bias + PUNYCODE_TMAX
462 PUNYCODE_TMAX
463 else
464 k - bias
465 end
466 )
467 break if q < t
468 output[out] =
469 punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
470 out += 1
471 q = (q - t) / (PUNYCODE_BASE - t)
472 k += PUNYCODE_BASE
473 end
474
475 output[out] = punycode_encode_digit(q)
476 out += 1
477 bias = punycode_adapt(delta, h + 1, h == b)
478 delta = 0
479 h += 1
480 end
481 end
482
483 delta += 1
484 n += 1
485 end
486
487 output_length[0] = out
488
489 outlen = out
490 outlen.times do |j|
491 c = output[j]
492 unless c >= 0 && c <= 127
493 raise StandardError, "Invalid output char."
494 end
495 unless PUNYCODE_PRINT_ASCII[c]
496 raise PunycodeBadInput, "Input is invalid."
497 end
498 end
499
500 output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
501 end
502 (class <<self; private :punycode_encode; end)
503
504 def self.punycode_decode(punycode)
505 input = []
506 output = []
507
508 if ACE_MAX_LENGTH * 2 < punycode.size
509 raise PunycodeBigOutput, "Output would exceed the space provided."
510 end
511 punycode.each_byte do |c|
512 unless c >= 0 && c <= 127
513 raise PunycodeBadInput, "Input is invalid."
514 end
515 input.push(c)
516 end
517
518 input_length = input.length
519 output_length = [UNICODE_MAX_LENGTH]
520
521 # Initialize the state
522 n = PUNYCODE_INITIAL_N
523
524 out = i = 0
525 max_out = output_length[0]
526 bias = PUNYCODE_INITIAL_BIAS
527
528 # Handle the basic code points: Let b be the number of input code
529 # points before the last delimiter, or 0 if there is none, then
530 # copy the first b code points to the output.
531
532 b = 0
533 input_length.times do |j|
534 b = j if punycode_delimiter?(input[j])
535 end
536 if b > max_out
537 raise PunycodeBigOutput, "Output would exceed the space provided."
538 end
539
540 b.times do |j|
541 unless punycode_basic?(input[j])
542 raise PunycodeBadInput, "Input is invalid."
543 end
544 output[out] = input[j]
545 out+=1
546 end
547
548 # Main decoding loop: Start just after the last delimiter if any
549 # basic code points were copied; start at the beginning otherwise.
550
551 in_ = b > 0 ? b + 1 : 0
552 while in_ < input_length
553
554 # in_ is the index of the next character to be consumed, and
555 # out is the number of code points in the output array.
556
557 # Decode a generalized variable-length integer into delta,
558 # which gets added to i. The overflow checking is easier
559 # if we increase i as we go, then subtract off its starting
560 # value at the end to obtain delta.
561
562 oldi = i; w = 1; k = PUNYCODE_BASE
563 while true
564 if in_ >= input_length
565 raise PunycodeBadInput, "Input is invalid."
566 end
567 digit = punycode_decode_digit(input[in_])
568 in_+=1
569 if digit >= PUNYCODE_BASE
570 raise PunycodeBadInput, "Input is invalid."
571 end
572 if digit > (PUNYCODE_MAXINT - i) / w
573 raise PunycodeOverflow, "Input needs wider integers to process."
574 end
575 i += digit * w
576 t = (
577 if k <= bias
578 PUNYCODE_TMIN
579 elsif k >= bias + PUNYCODE_TMAX
580 PUNYCODE_TMAX
581 else
582 k - bias
583 end
584 )
585 break if digit < t
586 if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t)
587 raise PunycodeOverflow, "Input needs wider integers to process."
588 end
589 w *= PUNYCODE_BASE - t
590 k += PUNYCODE_BASE
591 end
592
593 bias = punycode_adapt(i - oldi, out + 1, oldi == 0)
594
595 # I was supposed to wrap around from out + 1 to 0,
596 # incrementing n each time, so we'll fix that now:
597
598 if i / (out + 1) > PUNYCODE_MAXINT - n
599 raise PunycodeOverflow, "Input needs wider integers to process."
600 end
601 n += i / (out + 1)
602 i %= out + 1
603
604 # Insert n at position i of the output:
605
606 # not needed for Punycode:
607 # raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base
608 if out >= max_out
609 raise PunycodeBigOutput, "Output would exceed the space provided."
610 end
611
612 #memmove(output + i + 1, output + i, (out - i) * sizeof *output)
613 output[i + 1, out - i] = output[i, out - i]
614 output[i] = n
615 i += 1
616
617 out += 1
618 end
619
620 output_length[0] = out
621
622 output.pack("U*")
623 end
624 (class <<self; private :punycode_decode; end)
625
626 def self.punycode_basic?(codepoint)
627 codepoint < 0x80
628 end
629 (class <<self; private :punycode_basic?; end)
630
631 def self.punycode_delimiter?(codepoint)
632 codepoint == PUNYCODE_DELIMITER
633 end
634 (class <<self; private :punycode_delimiter?; end)
635
636 def self.punycode_encode_digit(d)
637 d + 22 + 75 * ((d < 26) ? 1 : 0)
638 end
639 (class <<self; private :punycode_encode_digit; end)
640
641 # Returns the numeric value of a basic codepoint
642 # (for use in representing integers) in the range 0 to
643 # base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
644 def self.punycode_decode_digit(codepoint)
645 if codepoint - 48 < 10
646 codepoint - 22
647 elsif codepoint - 65 < 26
648 codepoint - 65
649 elsif codepoint - 97 < 26
650 codepoint - 97
651 else
652 PUNYCODE_BASE
653 end
654 end
655 (class <<self; private :punycode_decode_digit; end)
656
657 # Bias adaptation method
658 def self.punycode_adapt(delta, numpoints, firsttime)
659 delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1
660 # delta >> 1 is a faster way of doing delta / 2
661 delta += delta / numpoints
662 difference = PUNYCODE_BASE - PUNYCODE_TMIN
663
664 k = 0
665 while delta > (difference * PUNYCODE_TMAX) / 2
666 delta /= difference
667 k += PUNYCODE_BASE
668 end
669
670 k + (difference + 1) * delta / (delta + PUNYCODE_SKEW)
671 end
672 (class <<self; private :punycode_adapt; end)
673 end
674 # :startdoc:
675 end
+0
-27
vendor/addressable/idna.rb less more
0 # frozen_string_literal: true
1
2 # encoding:utf-8
3 #--
4 # Copyright (C) Bob Aman
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #++
18
19
20 begin
21 require "addressable/idna/native"
22 rescue LoadError
23 # libidn or the idn gem was not available, fall back on a pure-Ruby
24 # implementation...
25 require "addressable/idna/pure"
26 end
+0
-1045
vendor/addressable/template.rb less more
0 # frozen_string_literal: true
1
2 # encoding:utf-8
3 #--
4 # Copyright (C) Bob Aman
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #++
18
19
20 require "addressable/version"
21 require "addressable/uri"
22
23 module Addressable
24 ##
25 # This is an implementation of a URI template based on
26 # RFC 6570 (http://tools.ietf.org/html/rfc6570).
27 class Template
28 # Constants used throughout the template code.
29 anything =
30 Addressable::URI::CharacterClasses::RESERVED +
31 Addressable::URI::CharacterClasses::UNRESERVED
32
33
34 variable_char_class =
35 Addressable::URI::CharacterClasses::ALPHA +
36 Addressable::URI::CharacterClasses::DIGIT + '_'
37
38 var_char =
39 "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
40 RESERVED =
41 "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
42 UNRESERVED =
43 "(?:[#{
44 Addressable::URI::CharacterClasses::UNRESERVED
45 }]|%[a-fA-F0-9][a-fA-F0-9])"
46 variable =
47 "(?:#{var_char}(?:\\.?#{var_char})*)"
48 varspec =
49 "(?:(#{variable})(\\*|:\\d+)?)"
50 VARNAME =
51 /^#{variable}$/
52 VARSPEC =
53 /^#{varspec}$/
54 VARIABLE_LIST =
55 /^#{varspec}(?:,#{varspec})*$/
56 operator =
57 "+#./;?&=,!@|"
58 EXPRESSION =
59 /\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
60
61
62 LEADERS = {
63 '?' => '?',
64 '/' => '/',
65 '#' => '#',
66 '.' => '.',
67 ';' => ';',
68 '&' => '&'
69 }
70 JOINERS = {
71 '?' => '&',
72 '.' => '.',
73 ';' => ';',
74 '&' => '&',
75 '/' => '/'
76 }
77
78 ##
79 # Raised if an invalid template value is supplied.
80 class InvalidTemplateValueError < StandardError
81 end
82
83 ##
84 # Raised if an invalid template operator is used in a pattern.
85 class InvalidTemplateOperatorError < StandardError
86 end
87
88 ##
89 # Raised if an invalid template operator is used in a pattern.
90 class TemplateOperatorAbortedError < StandardError
91 end
92
93 ##
94 # This class represents the data that is extracted when a Template
95 # is matched against a URI.
96 class MatchData
97 ##
98 # Creates a new MatchData object.
99 # MatchData objects should never be instantiated directly.
100 #
101 # @param [Addressable::URI] uri
102 # The URI that the template was matched against.
103 def initialize(uri, template, mapping)
104 @uri = uri.dup.freeze
105 @template = template
106 @mapping = mapping.dup.freeze
107 end
108
109 ##
110 # @return [Addressable::URI]
111 # The URI that the Template was matched against.
112 attr_reader :uri
113
114 ##
115 # @return [Addressable::Template]
116 # The Template used for the match.
117 attr_reader :template
118
119 ##
120 # @return [Hash]
121 # The mapping that resulted from the match.
122 # Note that this mapping does not include keys or values for
123 # variables that appear in the Template, but are not present
124 # in the URI.
125 attr_reader :mapping
126
127 ##
128 # @return [Array]
129 # The list of variables that were present in the Template.
130 # Note that this list will include variables which do not appear
131 # in the mapping because they were not present in URI.
132 def variables
133 self.template.variables
134 end
135 alias_method :keys, :variables
136 alias_method :names, :variables
137
138 ##
139 # @return [Array]
140 # The list of values that were captured by the Template.
141 # Note that this list will include nils for any variables which
142 # were in the Template, but did not appear in the URI.
143 def values
144 @values ||= self.variables.inject([]) do |accu, key|
145 accu << self.mapping[key]
146 accu
147 end
148 end
149 alias_method :captures, :values
150
151 ##
152 # Accesses captured values by name or by index.
153 #
154 # @param [String, Symbol, Fixnum] key
155 # Capture index or name. Note that when accessing by with index
156 # of 0, the full URI will be returned. The intention is to mimic
157 # the ::MatchData#[] behavior.
158 #
159 # @param [#to_int, nil] len
160 # If provided, an array of values will be returend with the given
161 # parameter used as length.
162 #
163 # @return [Array, String, nil]
164 # The captured value corresponding to the index or name. If the
165 # value was not provided or the key is unknown, nil will be
166 # returned.
167 #
168 # If the second parameter is provided, an array of that length will
169 # be returned instead.
170 def [](key, len = nil)
171 if len
172 to_a[key, len]
173 elsif String === key or Symbol === key
174 mapping[key.to_s]
175 else
176 to_a[key]
177 end
178 end
179
180 ##
181 # @return [Array]
182 # Array with the matched URI as first element followed by the captured
183 # values.
184 def to_a
185 [to_s, *values]
186 end
187
188 ##
189 # @return [String]
190 # The matched URI as String.
191 def to_s
192 uri.to_s
193 end
194 alias_method :string, :to_s
195
196 # Returns multiple captured values at once.
197 #
198 # @param [String, Symbol, Fixnum] *indexes
199 # Indices of the captures to be returned
200 #
201 # @return [Array]
202 # Values corresponding to given indices.
203 #
204 # @see Addressable::Template::MatchData#[]
205 def values_at(*indexes)
206 indexes.map { |i| self[i] }
207 end
208
209 ##
210 # Returns a <tt>String</tt> representation of the MatchData's state.
211 #
212 # @return [String] The MatchData's state, as a <tt>String</tt>.
213 def inspect
214 sprintf("#<%s:%#0x RESULT:%s>",
215 self.class.to_s, self.object_id, self.mapping.inspect)
216 end
217
218 ##
219 # Dummy method for code expecting a ::MatchData instance
220 #
221 # @return [String] An empty string.
222 def pre_match
223 ""
224 end
225 alias_method :post_match, :pre_match
226 end
227
228 ##
229 # Creates a new <tt>Addressable::Template</tt> object.
230 #
231 # @param [#to_str] pattern The URI Template pattern.
232 #
233 # @return [Addressable::Template] The initialized Template object.
234 def initialize(pattern)
235 if !pattern.respond_to?(:to_str)
236 raise TypeError, "Can't convert #{pattern.class} into String."
237 end
238 @pattern = pattern.to_str.dup.freeze
239 end
240
241 ##
242 # Freeze URI, initializing instance variables.
243 #
244 # @return [Addressable::URI] The frozen URI object.
245 def freeze
246 self.variables
247 self.variable_defaults
248 self.named_captures
249 super
250 end
251
252 ##
253 # @return [String] The Template object's pattern.
254 attr_reader :pattern
255
256 ##
257 # Returns a <tt>String</tt> representation of the Template object's state.
258 #
259 # @return [String] The Template object's state, as a <tt>String</tt>.
260 def inspect
261 sprintf("#<%s:%#0x PATTERN:%s>",
262 self.class.to_s, self.object_id, self.pattern)
263 end
264
265 ##
266 # Returns <code>true</code> if the Template objects are equal. This method
267 # does NOT normalize either Template before doing the comparison.
268 #
269 # @param [Object] template The Template to compare.
270 #
271 # @return [TrueClass, FalseClass]
272 # <code>true</code> if the Templates are equivalent, <code>false</code>
273 # otherwise.
274 def ==(template)
275 return false unless template.kind_of?(Template)
276 return self.pattern == template.pattern
277 end
278
279 ##
280 # Addressable::Template makes no distinction between `==` and `eql?`.
281 #
282 # @see #==
283 alias_method :eql?, :==
284
285 ##
286 # Extracts a mapping from the URI using a URI Template pattern.
287 #
288 # @param [Addressable::URI, #to_str] uri
289 # The URI to extract from.
290 #
291 # @param [#restore, #match] processor
292 # A template processor object may optionally be supplied.
293 #
294 # The object should respond to either the <tt>restore</tt> or
295 # <tt>match</tt> messages or both. The <tt>restore</tt> method should
296 # take two parameters: `[String] name` and `[String] value`.
297 # The <tt>restore</tt> method should reverse any transformations that
298 # have been performed on the value to ensure a valid URI.
299 # The <tt>match</tt> method should take a single
300 # parameter: `[String] name`. The <tt>match</tt> method should return
301 # a <tt>String</tt> containing a regular expression capture group for
302 # matching on that particular variable. The default value is `".*?"`.
303 # The <tt>match</tt> method has no effect on multivariate operator
304 # expansions.
305 #
306 # @return [Hash, NilClass]
307 # The <tt>Hash</tt> mapping that was extracted from the URI, or
308 # <tt>nil</tt> if the URI didn't match the template.
309 #
310 # @example
311 # class ExampleProcessor
312 # def self.restore(name, value)
313 # return value.gsub(/\+/, " ") if name == "query"
314 # return value
315 # end
316 #
317 # def self.match(name)
318 # return ".*?" if name == "first"
319 # return ".*"
320 # end
321 # end
322 #
323 # uri = Addressable::URI.parse(
324 # "http://example.com/search/an+example+search+query/"
325 # )
326 # Addressable::Template.new(
327 # "http://example.com/search/{query}/"
328 # ).extract(uri, ExampleProcessor)
329 # #=> {"query" => "an example search query"}
330 #
331 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
332 # Addressable::Template.new(
333 # "http://example.com/{first}/{second}/"
334 # ).extract(uri, ExampleProcessor)
335 # #=> {"first" => "a", "second" => "b/c"}
336 #
337 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
338 # Addressable::Template.new(
339 # "http://example.com/{first}/{-list|/|second}/"
340 # ).extract(uri)
341 # #=> {"first" => "a", "second" => ["b", "c"]}
342 def extract(uri, processor=nil)
343 match_data = self.match(uri, processor)
344 return (match_data ? match_data.mapping : nil)
345 end
346
347 ##
348 # Extracts match data from the URI using a URI Template pattern.
349 #
350 # @param [Addressable::URI, #to_str] uri
351 # The URI to extract from.
352 #
353 # @param [#restore, #match] processor
354 # A template processor object may optionally be supplied.
355 #
356 # The object should respond to either the <tt>restore</tt> or
357 # <tt>match</tt> messages or both. The <tt>restore</tt> method should
358 # take two parameters: `[String] name` and `[String] value`.
359 # The <tt>restore</tt> method should reverse any transformations that
360 # have been performed on the value to ensure a valid URI.
361 # The <tt>match</tt> method should take a single
362 # parameter: `[String] name`. The <tt>match</tt> method should return
363 # a <tt>String</tt> containing a regular expression capture group for
364 # matching on that particular variable. The default value is `".*?"`.
365 # The <tt>match</tt> method has no effect on multivariate operator
366 # expansions.
367 #
368 # @return [Hash, NilClass]
369 # The <tt>Hash</tt> mapping that was extracted from the URI, or
370 # <tt>nil</tt> if the URI didn't match the template.
371 #
372 # @example
373 # class ExampleProcessor
374 # def self.restore(name, value)
375 # return value.gsub(/\+/, " ") if name == "query"
376 # return value
377 # end
378 #
379 # def self.match(name)
380 # return ".*?" if name == "first"
381 # return ".*"
382 # end
383 # end
384 #
385 # uri = Addressable::URI.parse(
386 # "http://example.com/search/an+example+search+query/"
387 # )
388 # match = Addressable::Template.new(
389 # "http://example.com/search/{query}/"
390 # ).match(uri, ExampleProcessor)
391 # match.variables
392 # #=> ["query"]
393 # match.captures
394 # #=> ["an example search query"]
395 #
396 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
397 # match = Addressable::Template.new(
398 # "http://example.com/{first}/{+second}/"
399 # ).match(uri, ExampleProcessor)
400 # match.variables
401 # #=> ["first", "second"]
402 # match.captures
403 # #=> ["a", "b/c"]
404 #
405 # uri = Addressable::URI.parse("http://example.com/a/b/c/")
406 # match = Addressable::Template.new(
407 # "http://example.com/{first}{/second*}/"
408 # ).match(uri)
409 # match.variables
410 # #=> ["first", "second"]
411 # match.captures
412 # #=> ["a", ["b", "c"]]
413 def match(uri, processor=nil)
414 uri = Addressable::URI.parse(uri)
415 mapping = {}
416
417 # First, we need to process the pattern, and extract the values.
418 expansions, expansion_regexp =
419 parse_template_pattern(pattern, processor)
420
421 return nil unless uri.to_str.match(expansion_regexp)
422 unparsed_values = uri.to_str.scan(expansion_regexp).flatten
423
424 if uri.to_str == pattern
425 return Addressable::Template::MatchData.new(uri, self, mapping)
426 elsif expansions.size > 0
427 index = 0
428 expansions.each do |expansion|
429 _, operator, varlist = *expansion.match(EXPRESSION)
430 varlist.split(',').each do |varspec|
431 _, name, modifier = *varspec.match(VARSPEC)
432 mapping[name] ||= nil
433 case operator
434 when nil, '+', '#', '/', '.'
435 unparsed_value = unparsed_values[index]
436 name = varspec[VARSPEC, 1]
437 value = unparsed_value
438 value = value.split(JOINERS[operator]) if value && modifier == '*'
439 when ';', '?', '&'
440 if modifier == '*'
441 if unparsed_values[index]
442 value = unparsed_values[index].split(JOINERS[operator])
443 value = value.inject({}) do |acc, v|
444 key, val = v.split('=')
445 val = "" if val.nil?
446 acc[key] = val
447 acc
448 end
449 end
450 else
451 if (unparsed_values[index])
452 name, value = unparsed_values[index].split('=')
453 value = "" if value.nil?
454 end
455 end
456 end
457 if processor != nil && processor.respond_to?(:restore)
458 value = processor.restore(name, value)
459 end
460 if processor == nil
461 if value.is_a?(Hash)
462 value = value.inject({}){|acc, (k, v)|
463 acc[Addressable::URI.unencode_component(k)] =
464 Addressable::URI.unencode_component(v)
465 acc
466 }
467 elsif value.is_a?(Array)
468 value = value.map{|v| Addressable::URI.unencode_component(v) }
469 else
470 value = Addressable::URI.unencode_component(value)
471 end
472 end
473 if !mapping.has_key?(name) || mapping[name].nil?
474 # Doesn't exist, set to value (even if value is nil)
475 mapping[name] = value
476 end
477 index = index + 1
478 end
479 end
480 return Addressable::Template::MatchData.new(uri, self, mapping)
481 else
482 return nil
483 end
484 end
485
486 ##
487 # Expands a URI template into another URI template.
488 #
489 # @param [Hash] mapping The mapping that corresponds to the pattern.
490 # @param [#validate, #transform] processor
491 # An optional processor object may be supplied.
492 # @param [Boolean] normalize_values
493 # Optional flag to enable/disable unicode normalization. Default: true
494 #
495 # The object should respond to either the <tt>validate</tt> or
496 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
497 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
498 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
499 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
500 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
501 # exception will be raised if the value is invalid. The <tt>transform</tt>
502 # method should return the transformed variable value as a <tt>String</tt>.
503 # If a <tt>transform</tt> method is used, the value will not be percent
504 # encoded automatically. Unicode normalization will be performed both
505 # before and after sending the value to the transform method.
506 #
507 # @return [Addressable::Template] The partially expanded URI template.
508 #
509 # @example
510 # Addressable::Template.new(
511 # "http://example.com/{one}/{two}/"
512 # ).partial_expand({"one" => "1"}).pattern
513 # #=> "http://example.com/1/{two}/"
514 #
515 # Addressable::Template.new(
516 # "http://example.com/{?one,two}/"
517 # ).partial_expand({"one" => "1"}).pattern
518 # #=> "http://example.com/?one=1{&two}/"
519 #
520 # Addressable::Template.new(
521 # "http://example.com/{?one,two,three}/"
522 # ).partial_expand({"one" => "1", "three" => 3}).pattern
523 # #=> "http://example.com/?one=1{&two}&three=3"
524 def partial_expand(mapping, processor=nil, normalize_values=true)
525 result = self.pattern.dup
526 mapping = normalize_keys(mapping)
527 result.gsub!( EXPRESSION ) do |capture|
528 transform_partial_capture(mapping, capture, processor, normalize_values)
529 end
530 return Addressable::Template.new(result)
531 end
532
533 ##
534 # Expands a URI template into a full URI.
535 #
536 # @param [Hash] mapping The mapping that corresponds to the pattern.
537 # @param [#validate, #transform] processor
538 # An optional processor object may be supplied.
539 # @param [Boolean] normalize_values
540 # Optional flag to enable/disable unicode normalization. Default: true
541 #
542 # The object should respond to either the <tt>validate</tt> or
543 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
544 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
545 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
546 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
547 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
548 # exception will be raised if the value is invalid. The <tt>transform</tt>
549 # method should return the transformed variable value as a <tt>String</tt>.
550 # If a <tt>transform</tt> method is used, the value will not be percent
551 # encoded automatically. Unicode normalization will be performed both
552 # before and after sending the value to the transform method.
553 #
554 # @return [Addressable::URI] The expanded URI template.
555 #
556 # @example
557 # class ExampleProcessor
558 # def self.validate(name, value)
559 # return !!(value =~ /^[\w ]+$/) if name == "query"
560 # return true
561 # end
562 #
563 # def self.transform(name, value)
564 # return value.gsub(/ /, "+") if name == "query"
565 # return value
566 # end
567 # end
568 #
569 # Addressable::Template.new(
570 # "http://example.com/search/{query}/"
571 # ).expand(
572 # {"query" => "an example search query"},
573 # ExampleProcessor
574 # ).to_str
575 # #=> "http://example.com/search/an+example+search+query/"
576 #
577 # Addressable::Template.new(
578 # "http://example.com/search/{query}/"
579 # ).expand(
580 # {"query" => "an example search query"}
581 # ).to_str
582 # #=> "http://example.com/search/an%20example%20search%20query/"
583 #
584 # Addressable::Template.new(
585 # "http://example.com/search/{query}/"
586 # ).expand(
587 # {"query" => "bogus!"},
588 # ExampleProcessor
589 # ).to_str
590 # #=> Addressable::Template::InvalidTemplateValueError
591 def expand(mapping, processor=nil, normalize_values=true)
592 result = self.pattern.dup
593 mapping = normalize_keys(mapping)
594 result.gsub!( EXPRESSION ) do |capture|
595 transform_capture(mapping, capture, processor, normalize_values)
596 end
597 return Addressable::URI.parse(result)
598 end
599
600 ##
601 # Returns an Array of variables used within the template pattern.
602 # The variables are listed in the Array in the order they appear within
603 # the pattern. Multiple occurrences of a variable within a pattern are
604 # not represented in this Array.
605 #
606 # @return [Array] The variables present in the template's pattern.
607 def variables
608 @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
609 end
610 alias_method :keys, :variables
611 alias_method :names, :variables
612
613 ##
614 # Returns a mapping of variables to their default values specified
615 # in the template. Variables without defaults are not returned.
616 #
617 # @return [Hash] Mapping of template variables to their defaults
618 def variable_defaults
619 @variable_defaults ||=
620 Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
621 end
622
623 ##
624 # Coerces a template into a `Regexp` object. This regular expression will
625 # behave very similarly to the actual template, and should match the same
626 # URI values, but it cannot fully handle, for example, values that would
627 # extract to an `Array`.
628 #
629 # @return [Regexp] A regular expression which should match the template.
630 def to_regexp
631 _, source = parse_template_pattern(pattern)
632 Regexp.new(source)
633 end
634
635 ##
636 # Returns the source of the coerced `Regexp`.
637 #
638 # @return [String] The source of the `Regexp` given by {#to_regexp}.
639 #
640 # @api private
641 def source
642 self.to_regexp.source
643 end
644
645 ##
646 # Returns the named captures of the coerced `Regexp`.
647 #
648 # @return [Hash] The named captures of the `Regexp` given by {#to_regexp}.
649 #
650 # @api private
651 def named_captures
652 self.to_regexp.named_captures
653 end
654
655 ##
656 # Generates a route result for a given set of parameters.
657 # Should only be used by rack-mount.
658 #
659 # @param params [Hash] The set of parameters used to expand the template.
660 # @param recall [Hash] Default parameters used to expand the template.
661 # @param options [Hash] Either a `:processor` or a `:parameterize` block.
662 #
663 # @api private
664 def generate(params={}, recall={}, options={})
665 merged = recall.merge(params)
666 if options[:processor]
667 processor = options[:processor]
668 elsif options[:parameterize]
669 # TODO: This is sending me into fits trying to shoe-horn this into
670 # the existing API. I think I've got this backwards and processors
671 # should be a set of 4 optional blocks named :validate, :transform,
672 # :match, and :restore. Having to use a singleton here is a huge
673 # code smell.
674 processor = Object.new
675 class <<processor
676 attr_accessor :block
677 def transform(name, value)
678 block.call(name, value)
679 end
680 end
681 processor.block = options[:parameterize]
682 else
683 processor = nil
684 end
685 result = self.expand(merged, processor)
686 result.to_s if result
687 end
688
689 private
690 def ordered_variable_defaults
691 @ordered_variable_defaults ||= begin
692 expansions, _ = parse_template_pattern(pattern)
693 expansions.map do |capture|
694 _, _, varlist = *capture.match(EXPRESSION)
695 varlist.split(',').map do |varspec|
696 varspec[VARSPEC, 1]
697 end
698 end.flatten
699 end
700 end
701
702
703 ##
704 # Loops through each capture and expands any values available in mapping
705 #
706 # @param [Hash] mapping
707 # Set of keys to expand
708 # @param [String] capture
709 # The expression to expand
710 # @param [#validate, #transform] processor
711 # An optional processor object may be supplied.
712 # @param [Boolean] normalize_values
713 # Optional flag to enable/disable unicode normalization. Default: true
714 #
715 # The object should respond to either the <tt>validate</tt> or
716 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
717 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
718 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
719 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
720 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
721 # will be raised if the value is invalid. The <tt>transform</tt> method
722 # should return the transformed variable value as a <tt>String</tt>. If a
723 # <tt>transform</tt> method is used, the value will not be percent encoded
724 # automatically. Unicode normalization will be performed both before and
725 # after sending the value to the transform method.
726 #
727 # @return [String] The expanded expression
728 def transform_partial_capture(mapping, capture, processor = nil,
729 normalize_values = true)
730 _, operator, varlist = *capture.match(EXPRESSION)
731
732 vars = varlist.split(",")
733
734 if operator == "?"
735 # partial expansion of form style query variables sometimes requires a
736 # slight reordering of the variables to produce a valid url.
737 first_to_expand = vars.find { |varspec|
738 _, name, _ = *varspec.match(VARSPEC)
739 mapping.key?(name) && !mapping[name].nil?
740 }
741
742 vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
743 end
744
745 vars.
746 inject("".dup) do |acc, varspec|
747 _, name, _ = *varspec.match(VARSPEC)
748 next_val = if mapping.key? name
749 transform_capture(mapping, "{#{operator}#{varspec}}",
750 processor, normalize_values)
751 else
752 "{#{operator}#{varspec}}"
753 end
754 # If we've already expanded at least one '?' operator with non-empty
755 # value, change to '&'
756 operator = "&" if (operator == "?") && (next_val != "")
757 acc << next_val
758 end
759 end
760
761 ##
762 # Transforms a mapped value so that values can be substituted into the
763 # template.
764 #
765 # @param [Hash] mapping The mapping to replace captures
766 # @param [String] capture
767 # The expression to replace
768 # @param [#validate, #transform] processor
769 # An optional processor object may be supplied.
770 # @param [Boolean] normalize_values
771 # Optional flag to enable/disable unicode normalization. Default: true
772 #
773 #
774 # The object should respond to either the <tt>validate</tt> or
775 # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
776 # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
777 # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
778 # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
779 # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
780 # will be raised if the value is invalid. The <tt>transform</tt> method
781 # should return the transformed variable value as a <tt>String</tt>. If a
782 # <tt>transform</tt> method is used, the value will not be percent encoded
783 # automatically. Unicode normalization will be performed both before and
784 # after sending the value to the transform method.
785 #
786 # @return [String] The expanded expression
787 def transform_capture(mapping, capture, processor=nil,
788 normalize_values=true)
789 _, operator, varlist = *capture.match(EXPRESSION)
790 return_value = varlist.split(',').inject([]) do |acc, varspec|
791 _, name, modifier = *varspec.match(VARSPEC)
792 value = mapping[name]
793 unless value == nil || value == {}
794 allow_reserved = %w(+ #).include?(operator)
795 # Common primitives where the .to_s output is well-defined
796 if Numeric === value || Symbol === value ||
797 value == true || value == false
798 value = value.to_s
799 end
800 length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
801
802 unless (Hash === value) ||
803 value.respond_to?(:to_ary) || value.respond_to?(:to_str)
804 raise TypeError,
805 "Can't convert #{value.class} into String or Array."
806 end
807
808 value = normalize_value(value) if normalize_values
809
810 if processor == nil || !processor.respond_to?(:transform)
811 # Handle percent escaping
812 if allow_reserved
813 encode_map =
814 Addressable::URI::CharacterClasses::RESERVED +
815 Addressable::URI::CharacterClasses::UNRESERVED
816 else
817 encode_map = Addressable::URI::CharacterClasses::UNRESERVED
818 end
819 if value.kind_of?(Array)
820 transformed_value = value.map do |val|
821 if length
822 Addressable::URI.encode_component(val[0...length], encode_map)
823 else
824 Addressable::URI.encode_component(val, encode_map)
825 end
826 end
827 unless modifier == "*"
828 transformed_value = transformed_value.join(',')
829 end
830 elsif value.kind_of?(Hash)
831 transformed_value = value.map do |key, val|
832 if modifier == "*"
833 "#{
834 Addressable::URI.encode_component( key, encode_map)
835 }=#{
836 Addressable::URI.encode_component( val, encode_map)
837 }"
838 else
839 "#{
840 Addressable::URI.encode_component( key, encode_map)
841 },#{
842 Addressable::URI.encode_component( val, encode_map)
843 }"
844 end
845 end
846 unless modifier == "*"
847 transformed_value = transformed_value.join(',')
848 end
849 else
850 if length
851 transformed_value = Addressable::URI.encode_component(
852 value[0...length], encode_map)
853 else
854 transformed_value = Addressable::URI.encode_component(
855 value, encode_map)
856 end
857 end
858 end
859
860 # Process, if we've got a processor
861 if processor != nil
862 if processor.respond_to?(:validate)
863 if !processor.validate(name, value)
864 display_value = value.kind_of?(Array) ? value.inspect : value
865 raise InvalidTemplateValueError,
866 "#{name}=#{display_value} is an invalid template value."
867 end
868 end
869 if processor.respond_to?(:transform)
870 transformed_value = processor.transform(name, value)
871 if normalize_values
872 transformed_value = normalize_value(transformed_value)
873 end
874 end
875 end
876 acc << [name, transformed_value]
877 end
878 acc
879 end
880 return "" if return_value.empty?
881 join_values(operator, return_value)
882 end
883
884 ##
885 # Takes a set of values, and joins them together based on the
886 # operator.
887 #
888 # @param [String, Nil] operator One of the operators from the set
889 # (?,&,+,#,;,/,.), or nil if there wasn't one.
890 # @param [Array] return_value
891 # The set of return values (as [variable_name, value] tuples) that will
892 # be joined together.
893 #
894 # @return [String] The transformed mapped value
895 def join_values(operator, return_value)
896 leader = LEADERS.fetch(operator, '')
897 joiner = JOINERS.fetch(operator, ',')
898 case operator
899 when '&', '?'
900 leader + return_value.map{|k,v|
901 if v.is_a?(Array) && v.first =~ /=/
902 v.join(joiner)
903 elsif v.is_a?(Array)
904 v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
905 else
906 "#{k}=#{v}"
907 end
908 }.join(joiner)
909 when ';'
910 return_value.map{|k,v|
911 if v.is_a?(Array) && v.first =~ /=/
912 ';' + v.join(";")
913 elsif v.is_a?(Array)
914 ';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
915 else
916 v && v != '' ? ";#{k}=#{v}" : ";#{k}"
917 end
918 }.join
919 else
920 leader + return_value.map{|k,v| v}.join(joiner)
921 end
922 end
923
924 ##
925 # Takes a set of values, and joins them together based on the
926 # operator.
927 #
928 # @param [Hash, Array, String] value
929 # Normalizes keys and values with IDNA#unicode_normalize_kc
930 #
931 # @return [Hash, Array, String] The normalized values
932 def normalize_value(value)
933 unless value.is_a?(Hash)
934 value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
935 end
936
937 # Handle unicode normalization
938 if value.kind_of?(Array)
939 value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
940 elsif value.kind_of?(Hash)
941 value = value.inject({}) { |acc, (k, v)|
942 acc[Addressable::IDNA.unicode_normalize_kc(k)] =
943 Addressable::IDNA.unicode_normalize_kc(v)
944 acc
945 }
946 else
947 value = Addressable::IDNA.unicode_normalize_kc(value)
948 end
949 value
950 end
951
952 ##
953 # Generates a hash with string keys
954 #
955 # @param [Hash] mapping A mapping hash to normalize
956 #
957 # @return [Hash]
958 # A hash with stringified keys
959 def normalize_keys(mapping)
960 return mapping.inject({}) do |accu, pair|
961 name, value = pair
962 if Symbol === name
963 name = name.to_s
964 elsif name.respond_to?(:to_str)
965 name = name.to_str
966 else
967 raise TypeError,
968 "Can't convert #{name.class} into String."
969 end
970 accu[name] = value
971 accu
972 end
973 end
974
975 ##
976 # Generates the <tt>Regexp</tt> that parses a template pattern.
977 #
978 # @param [String] pattern The URI template pattern.
979 # @param [#match] processor The template processor to use.
980 #
981 # @return [Regexp]
982 # A regular expression which may be used to parse a template pattern.
983 def parse_template_pattern(pattern, processor=nil)
984 # Escape the pattern. The two gsubs restore the escaped curly braces
985 # back to their original form. Basically, escape everything that isn't
986 # within an expansion.
987 escaped_pattern = Regexp.escape(
988 pattern
989 ).gsub(/\\\{(.*?)\\\}/) do |escaped|
990 escaped.gsub(/\\(.)/, "\\1")
991 end
992
993 expansions = []
994
995 # Create a regular expression that captures the values of the
996 # variables in the URI.
997 regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
998
999 expansions << expansion
1000 _, operator, varlist = *expansion.match(EXPRESSION)
1001 leader = Regexp.escape(LEADERS.fetch(operator, ''))
1002 joiner = Regexp.escape(JOINERS.fetch(operator, ','))
1003 combined = varlist.split(',').map do |varspec|
1004 _, name, modifier = *varspec.match(VARSPEC)
1005
1006 result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
1007 if result
1008 "(?<#{name}>#{ result })"
1009 else
1010 group = case operator
1011 when '+'
1012 "#{ RESERVED }*?"
1013 when '#'
1014 "#{ RESERVED }*?"
1015 when '/'
1016 "#{ UNRESERVED }*?"
1017 when '.'
1018 "#{ UNRESERVED.gsub('\.', '') }*?"
1019 when ';'
1020 "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
1021 when '?'
1022 "#{ UNRESERVED }*=#{ UNRESERVED }*?"
1023 when '&'
1024 "#{ UNRESERVED }*=#{ UNRESERVED }*?"
1025 else
1026 "#{ UNRESERVED }*?"
1027 end
1028 if modifier == '*'
1029 "(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
1030 else
1031 "(?<#{name}>#{group})?"
1032 end
1033 end
1034 end.join("#{joiner}?")
1035 "(?:|#{leader}#{combined})"
1036 end
1037
1038 # Ensure that the regular expression matches the whole URI.
1039 regexp_string = "^#{regexp_string}$"
1040 return expansions, Regexp.new(regexp_string)
1041 end
1042
1043 end
1044 end
+0
-2510
vendor/addressable/uri.rb less more
0 # frozen_string_literal: true
1
2 # encoding:utf-8
3 #--
4 # Copyright (C) Bob Aman
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #++
18
19
20 require "addressable/version"
21 require "addressable/idna"
22 require "public_suffix"
23
24 ##
25 # Addressable is a library for processing links and URIs.
26 module Addressable
27 ##
28 # This is an implementation of a URI parser based on
29 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
30 # <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
31 class URI
32 ##
33 # Raised if something other than a uri is supplied.
34 class InvalidURIError < StandardError
35 end
36
37 ##
38 # Container for the character classes specified in
39 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
40 module CharacterClasses
41 ALPHA = "a-zA-Z"
42 DIGIT = "0-9"
43 GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
44 SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
45 RESERVED = GEN_DELIMS + SUB_DELIMS
46 UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
47 PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
48 SCHEME = ALPHA + DIGIT + "\\-\\+\\."
49 HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]"
50 AUTHORITY = PCHAR
51 PATH = PCHAR + "\\/"
52 QUERY = PCHAR + "\\/\\?"
53 FRAGMENT = PCHAR + "\\/\\?"
54 end
55
56 SLASH = '/'
57 EMPTY_STR = ''
58
59 URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
60
61 PORT_MAPPING = {
62 "http" => 80,
63 "https" => 443,
64 "ftp" => 21,
65 "tftp" => 69,
66 "sftp" => 22,
67 "ssh" => 22,
68 "svn+ssh" => 22,
69 "telnet" => 23,
70 "nntp" => 119,
71 "gopher" => 70,
72 "wais" => 210,
73 "ldap" => 389,
74 "prospero" => 1525
75 }
76
77 ##
78 # Returns a URI object based on the parsed string.
79 #
80 # @param [String, Addressable::URI, #to_str] uri
81 # The URI string to parse.
82 # No parsing is performed if the object is already an
83 # <code>Addressable::URI</code>.
84 #
85 # @return [Addressable::URI] The parsed URI.
86 def self.parse(uri)
87 # If we were given nil, return nil.
88 return nil unless uri
89 # If a URI object is passed, just return itself.
90 return uri.dup if uri.kind_of?(self)
91
92 # If a URI object of the Ruby standard library variety is passed,
93 # convert it to a string, then parse the string.
94 # We do the check this way because we don't want to accidentally
95 # cause a missing constant exception to be thrown.
96 if uri.class.name =~ /^URI\b/
97 uri = uri.to_s
98 end
99
100 # Otherwise, convert to a String
101 begin
102 uri = uri.to_str
103 rescue TypeError, NoMethodError
104 raise TypeError, "Can't convert #{uri.class} into String."
105 end if not uri.is_a? String
106
107 # This Regexp supplied as an example in RFC 3986, and it works great.
108 scan = uri.scan(URIREGEX)
109 fragments = scan[0]
110 scheme = fragments[1]
111 authority = fragments[3]
112 path = fragments[4]
113 query = fragments[6]
114 fragment = fragments[8]
115 user = nil
116 password = nil
117 host = nil
118 port = nil
119 if authority != nil
120 # The Regexp above doesn't split apart the authority.
121 userinfo = authority[/^([^\[\]]*)@/, 1]
122 if userinfo != nil
123 user = userinfo.strip[/^([^:]*):?/, 1]
124 password = userinfo.strip[/:(.*)$/, 1]
125 end
126 host = authority.sub(
127 /^([^\[\]]*)@/, EMPTY_STR
128 ).sub(
129 /:([^:@\[\]]*?)$/, EMPTY_STR
130 )
131 port = authority[/:([^:@\[\]]*?)$/, 1]
132 end
133 if port == EMPTY_STR
134 port = nil
135 end
136
137 return new(
138 :scheme => scheme,
139 :user => user,
140 :password => password,
141 :host => host,
142 :port => port,
143 :path => path,
144 :query => query,
145 :fragment => fragment
146 )
147 end
148
149 ##
150 # Converts an input to a URI. The input does not have to be a valid
151 # URI — the method will use heuristics to guess what URI was intended.
152 # This is not standards-compliant, merely user-friendly.
153 #
154 # @param [String, Addressable::URI, #to_str] uri
155 # The URI string to parse.
156 # No parsing is performed if the object is already an
157 # <code>Addressable::URI</code>.
158 # @param [Hash] hints
159 # A <code>Hash</code> of hints to the heuristic parser.
160 # Defaults to <code>{:scheme => "http"}</code>.
161 #
162 # @return [Addressable::URI] The parsed URI.
163 def self.heuristic_parse(uri, hints={})
164 # If we were given nil, return nil.
165 return nil unless uri
166 # If a URI object is passed, just return itself.
167 return uri.dup if uri.kind_of?(self)
168
169 # If a URI object of the Ruby standard library variety is passed,
170 # convert it to a string, then parse the string.
171 # We do the check this way because we don't want to accidentally
172 # cause a missing constant exception to be thrown.
173 if uri.class.name =~ /^URI\b/
174 uri = uri.to_s
175 end
176
177 if !uri.respond_to?(:to_str)
178 raise TypeError, "Can't convert #{uri.class} into String."
179 end
180 # Otherwise, convert to a String
181 uri = uri.to_str.dup.strip
182 hints = {
183 :scheme => "http"
184 }.merge(hints)
185 case uri
186 when /^http:\//i
187 uri.sub!(/^http:\/+/i, "http://")
188 when /^https:\//i
189 uri.sub!(/^https:\/+/i, "https://")
190 when /^feed:\/+http:\//i
191 uri.sub!(/^feed:\/+http:\/+/i, "feed:http://")
192 when /^feed:\//i
193 uri.sub!(/^feed:\/+/i, "feed://")
194 when %r[^file:/{4}]i
195 uri.sub!(%r[^file:/+]i, "file:////")
196 when %r[^file://localhost/]i
197 uri.sub!(%r[^file://localhost/+]i, "file:///")
198 when %r[^file:/+]i
199 uri.sub!(%r[^file:/+]i, "file:///")
200 when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
201 uri.sub!(/^/, hints[:scheme] + "://")
202 when /\A\d+\..*:\d+\z/
203 uri = "#{hints[:scheme]}://#{uri}"
204 end
205 match = uri.match(URIREGEX)
206 fragments = match.captures
207 authority = fragments[3]
208 if authority && authority.length > 0
209 new_authority = authority.gsub(/\\/, '/').gsub(/ /, '%20')
210 # NOTE: We want offset 4, not 3!
211 offset = match.offset(4)
212 uri = uri.dup
213 uri[offset[0]...offset[1]] = new_authority
214 end
215 parsed = self.parse(uri)
216 if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
217 parsed = self.parse(hints[:scheme] + "://" + uri)
218 end
219 if parsed.path.include?(".")
220 new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
221 if new_host
222 parsed.defer_validation do
223 new_path = parsed.path.sub(
224 Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
225 parsed.host = new_host
226 parsed.path = new_path
227 parsed.scheme = hints[:scheme] unless parsed.scheme
228 end
229 end
230 end
231 return parsed
232 end
233
234 ##
235 # Converts a path to a file scheme URI. If the path supplied is
236 # relative, it will be returned as a relative URI. If the path supplied
237 # is actually a non-file URI, it will parse the URI as if it had been
238 # parsed with <code>Addressable::URI.parse</code>. Handles all of the
239 # various Microsoft-specific formats for specifying paths.
240 #
241 # @param [String, Addressable::URI, #to_str] path
242 # Typically a <code>String</code> path to a file or directory, but
243 # will return a sensible return value if an absolute URI is supplied
244 # instead.
245 #
246 # @return [Addressable::URI]
247 # The parsed file scheme URI or the original URI if some other URI
248 # scheme was provided.
249 #
250 # @example
251 # base = Addressable::URI.convert_path("/absolute/path/")
252 # uri = Addressable::URI.convert_path("relative/path")
253 # (base + uri).to_s
254 # #=> "file:///absolute/path/relative/path"
255 #
256 # Addressable::URI.convert_path(
257 # "c:\\windows\\My Documents 100%20\\foo.txt"
258 # ).to_s
259 # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
260 #
261 # Addressable::URI.convert_path("http://example.com/").to_s
262 # #=> "http://example.com/"
263 def self.convert_path(path)
264 # If we were given nil, return nil.
265 return nil unless path
266 # If a URI object is passed, just return itself.
267 return path if path.kind_of?(self)
268 if !path.respond_to?(:to_str)
269 raise TypeError, "Can't convert #{path.class} into String."
270 end
271 # Otherwise, convert to a String
272 path = path.to_str.strip
273
274 path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
275 path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
276 uri = self.parse(path)
277
278 if uri.scheme == nil
279 # Adjust windows-style uris
280 uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
281 "/#{$1.downcase}:/"
282 end
283 uri.path.gsub!(/\\/, SLASH)
284 if File.exist?(uri.path) &&
285 File.stat(uri.path).directory?
286 uri.path.sub!(/\/$/, EMPTY_STR)
287 uri.path = uri.path + '/'
288 end
289
290 # If the path is absolute, set the scheme and host.
291 if uri.path =~ /^\//
292 uri.scheme = "file"
293 uri.host = EMPTY_STR
294 end
295 uri.normalize!
296 end
297
298 return uri
299 end
300
301 ##
302 # Joins several URIs together.
303 #
304 # @param [String, Addressable::URI, #to_str] *uris
305 # The URIs to join.
306 #
307 # @return [Addressable::URI] The joined URI.
308 #
309 # @example
310 # base = "http://example.com/"
311 # uri = Addressable::URI.parse("relative/path")
312 # Addressable::URI.join(base, uri)
313 # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
314 def self.join(*uris)
315 uri_objects = uris.collect do |uri|
316 if !uri.respond_to?(:to_str)
317 raise TypeError, "Can't convert #{uri.class} into String."
318 end
319 uri.kind_of?(self) ? uri : self.parse(uri.to_str)
320 end
321 result = uri_objects.shift.dup
322 for uri in uri_objects
323 result.join!(uri)
324 end
325 return result
326 end
327
328 ##
329 # Percent encodes a URI component.
330 #
331 # @param [String, #to_str] component The URI component to encode.
332 #
333 # @param [String, Regexp] character_class
334 # The characters which are not percent encoded. If a <code>String</code>
335 # is passed, the <code>String</code> must be formatted as a regular
336 # expression character class. (Do not include the surrounding square
337 # brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
338 # everything but the letters 'b' through 'z' and the numbers '0' through
339 # '9' to be percent encoded. If a <code>Regexp</code> is passed, the
340 # value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A set of
341 # useful <code>String</code> values may be found in the
342 # <code>Addressable::URI::CharacterClasses</code> module. The default
343 # value is the reserved plus unreserved character classes specified in
344 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
345 #
346 # @param [Regexp] upcase_encoded
347 # A string of characters that may already be percent encoded, and whose
348 # encodings should be upcased. This allows normalization of percent
349 # encodings for characters not included in the
350 # <code>character_class</code>.
351 #
352 # @return [String] The encoded component.
353 #
354 # @example
355 # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
356 # => "simple%2Fex%61mple"
357 # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
358 # => "simple%2Fex%61mple"
359 # Addressable::URI.encode_component(
360 # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
361 # )
362 # => "simple%2Fexample"
363 def self.encode_component(component, character_class=
364 CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
365 upcase_encoded='')
366 return nil if component.nil?
367
368 begin
369 if component.kind_of?(Symbol) ||
370 component.kind_of?(Numeric) ||
371 component.kind_of?(TrueClass) ||
372 component.kind_of?(FalseClass)
373 component = component.to_s
374 else
375 component = component.to_str
376 end
377 rescue TypeError, NoMethodError
378 raise TypeError, "Can't convert #{component.class} into String."
379 end if !component.is_a? String
380
381 if ![String, Regexp].include?(character_class.class)
382 raise TypeError,
383 "Expected String or Regexp, got #{character_class.inspect}"
384 end
385 if character_class.kind_of?(String)
386 character_class = /[^#{character_class}]/
387 end
388 # We can't perform regexps on invalid UTF sequences, but
389 # here we need to, so switch to ASCII.
390 component = component.dup
391 component.force_encoding(Encoding::ASCII_8BIT)
392 # Avoiding gsub! because there are edge cases with frozen strings
393 component = component.gsub(character_class) do |sequence|
394 (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
395 end
396 if upcase_encoded.length > 0
397 component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
398 char.unpack('C*').map { |c| '%02x' % c }.join
399 end.join('|')})/i) { |s| s.upcase }
400 end
401 return component
402 end
403
404 class << self
405 alias_method :encode_component, :encode_component
406 end
407
408 ##
409 # Unencodes any percent encoded characters within a URI component.
410 # This method may be used for unencoding either components or full URIs,
411 # however, it is recommended to use the <code>unencode_component</code>
412 # alias when unencoding components.
413 #
414 # @param [String, Addressable::URI, #to_str] uri
415 # The URI or component to unencode.
416 #
417 # @param [Class] return_type
418 # The type of object to return.
419 # This value may only be set to <code>String</code> or
420 # <code>Addressable::URI</code>. All other values are invalid. Defaults
421 # to <code>String</code>.
422 #
423 # @param [String] leave_encoded
424 # A string of characters to leave encoded. If a percent encoded character
425 # in this list is encountered then it will remain percent encoded.
426 #
427 # @return [String, Addressable::URI]
428 # The unencoded component or URI.
429 # The return type is determined by the <code>return_type</code>
430 # parameter.
431 def self.unencode(uri, return_type=String, leave_encoded='')
432 return nil if uri.nil?
433
434 begin
435 uri = uri.to_str
436 rescue NoMethodError, TypeError
437 raise TypeError, "Can't convert #{uri.class} into String."
438 end if !uri.is_a? String
439 if ![String, ::Addressable::URI].include?(return_type)
440 raise TypeError,
441 "Expected Class (String or Addressable::URI), " +
442 "got #{return_type.inspect}"
443 end
444 uri = uri.dup
445 # Seriously, only use UTF-8. I'm really not kidding!
446 uri.force_encoding("utf-8")
447 leave_encoded = leave_encoded.dup.force_encoding("utf-8")
448 result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
449 c = sequence[1..3].to_i(16).chr
450 c.force_encoding("utf-8")
451 leave_encoded.include?(c) ? sequence : c
452 end
453 result.force_encoding("utf-8")
454 if return_type == String
455 return result
456 elsif return_type == ::Addressable::URI
457 return ::Addressable::URI.parse(result)
458 end
459 end
460
461 class << self
462 alias_method :unescape, :unencode
463 alias_method :unencode_component, :unencode
464 alias_method :unescape_component, :unencode
465 end
466
467
468 ##
469 # Normalizes the encoding of a URI component.
470 #
471 # @param [String, #to_str] component The URI component to encode.
472 #
473 # @param [String, Regexp] character_class
474 # The characters which are not percent encoded. If a <code>String</code>
475 # is passed, the <code>String</code> must be formatted as a regular
476 # expression character class. (Do not include the surrounding square
477 # brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
478 # everything but the letters 'b' through 'z' and the numbers '0'
479 # through '9' to be percent encoded. If a <code>Regexp</code> is passed,
480 # the value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
481 # set of useful <code>String</code> values may be found in the
482 # <code>Addressable::URI::CharacterClasses</code> module. The default
483 # value is the reserved plus unreserved character classes specified in
484 # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
485 #
486 # @param [String] leave_encoded
487 # When <code>character_class</code> is a <code>String</code> then
488 # <code>leave_encoded</code> is a string of characters that should remain
489 # percent encoded while normalizing the component; if they appear percent
490 # encoded in the original component, then they will be upcased ("%2f"
491 # normalized to "%2F") but otherwise left alone.
492 #
493 # @return [String] The normalized component.
494 #
495 # @example
496 # Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z")
497 # => "simple%2Fex%61mple"
498 # Addressable::URI.normalize_component(
499 # "simpl%65/%65xampl%65", /[^b-zB-Z]/
500 # )
501 # => "simple%2Fex%61mple"
502 # Addressable::URI.normalize_component(
503 # "simpl%65/%65xampl%65",
504 # Addressable::URI::CharacterClasses::UNRESERVED
505 # )
506 # => "simple%2Fexample"
507 # Addressable::URI.normalize_component(
508 # "one%20two%2fthree%26four",
509 # "0-9a-zA-Z &/",
510 # "/"
511 # )
512 # => "one two%2Fthree&four"
513 def self.normalize_component(component, character_class=
514 CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
515 leave_encoded='')
516 return nil if component.nil?
517
518 begin
519 component = component.to_str
520 rescue NoMethodError, TypeError
521 raise TypeError, "Can't convert #{component.class} into String."
522 end if !component.is_a? String
523
524 if ![String, Regexp].include?(character_class.class)
525 raise TypeError,
526 "Expected String or Regexp, got #{character_class.inspect}"
527 end
528 if character_class.kind_of?(String)
529 leave_re = if leave_encoded.length > 0
530 character_class = "#{character_class}%" unless character_class.include?('%')
531
532 "|%(?!#{leave_encoded.chars.map do |char|
533 seq = char.unpack('C*').map { |c| '%02x' % c }.join
534 [seq.upcase, seq.downcase]
535 end.flatten.join('|')})"
536 end
537
538 character_class = /[^#{character_class}]#{leave_re}/
539 end
540 # We can't perform regexps on invalid UTF sequences, but
541 # here we need to, so switch to ASCII.
542 component = component.dup
543 component.force_encoding(Encoding::ASCII_8BIT)
544 unencoded = self.unencode_component(component, String, leave_encoded)
545 begin
546 encoded = self.encode_component(
547 Addressable::IDNA.unicode_normalize_kc(unencoded),
548 character_class,
549 leave_encoded
550 )
551 rescue ArgumentError
552 encoded = self.encode_component(unencoded)
553 end
554 encoded.force_encoding(Encoding::UTF_8)
555 return encoded
556 end
557
558 ##
559 # Percent encodes any special characters in the URI.
560 #
561 # @param [String, Addressable::URI, #to_str] uri
562 # The URI to encode.
563 #
564 # @param [Class] return_type
565 # The type of object to return.
566 # This value may only be set to <code>String</code> or
567 # <code>Addressable::URI</code>. All other values are invalid. Defaults
568 # to <code>String</code>.
569 #
570 # @return [String, Addressable::URI]
571 # The encoded URI.
572 # The return type is determined by the <code>return_type</code>
573 # parameter.
574 def self.encode(uri, return_type=String)
575 return nil if uri.nil?
576
577 begin
578 uri = uri.to_str
579 rescue NoMethodError, TypeError
580 raise TypeError, "Can't convert #{uri.class} into String."
581 end if !uri.is_a? String
582
583 if ![String, ::Addressable::URI].include?(return_type)
584 raise TypeError,
585 "Expected Class (String or Addressable::URI), " +
586 "got #{return_type.inspect}"
587 end
588 uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
589 encoded_uri = Addressable::URI.new(
590 :scheme => self.encode_component(uri_object.scheme,
591 Addressable::URI::CharacterClasses::SCHEME),
592 :authority => self.encode_component(uri_object.authority,
593 Addressable::URI::CharacterClasses::AUTHORITY),
594 :path => self.encode_component(uri_object.path,
595 Addressable::URI::CharacterClasses::PATH),
596 :query => self.encode_component(uri_object.query,
597 Addressable::URI::CharacterClasses::QUERY),
598 :fragment => self.encode_component(uri_object.fragment,
599 Addressable::URI::CharacterClasses::FRAGMENT)
600 )
601 if return_type == String
602 return encoded_uri.to_s
603 elsif return_type == ::Addressable::URI
604 return encoded_uri
605 end
606 end
607
608 class << self
609 alias_method :escape, :encode
610 end
611
612 ##
613 # Normalizes the encoding of a URI. Characters within a hostname are
614 # not percent encoded to allow for internationalized domain names.
615 #
616 # @param [String, Addressable::URI, #to_str] uri
617 # The URI to encode.
618 #
619 # @param [Class] return_type
620 # The type of object to return.
621 # This value may only be set to <code>String</code> or
622 # <code>Addressable::URI</code>. All other values are invalid. Defaults
623 # to <code>String</code>.
624 #
625 # @return [String, Addressable::URI]
626 # The encoded URI.
627 # The return type is determined by the <code>return_type</code>
628 # parameter.
629 def self.normalized_encode(uri, return_type=String)
630 begin
631 uri = uri.to_str
632 rescue NoMethodError, TypeError
633 raise TypeError, "Can't convert #{uri.class} into String."
634 end if !uri.is_a? String
635
636 if ![String, ::Addressable::URI].include?(return_type)
637 raise TypeError,
638 "Expected Class (String or Addressable::URI), " +
639 "got #{return_type.inspect}"
640 end
641 uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
642 components = {
643 :scheme => self.unencode_component(uri_object.scheme),
644 :user => self.unencode_component(uri_object.user),
645 :password => self.unencode_component(uri_object.password),
646 :host => self.unencode_component(uri_object.host),
647 :port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
648 :path => self.unencode_component(uri_object.path),
649 :query => self.unencode_component(uri_object.query),
650 :fragment => self.unencode_component(uri_object.fragment)
651 }
652 components.each do |key, value|
653 if value != nil
654 begin
655 components[key] =
656 Addressable::IDNA.unicode_normalize_kc(value.to_str)
657 rescue ArgumentError
658 # Likely a malformed UTF-8 character, skip unicode normalization
659 components[key] = value.to_str
660 end
661 end
662 end
663 encoded_uri = Addressable::URI.new(
664 :scheme => self.encode_component(components[:scheme],
665 Addressable::URI::CharacterClasses::SCHEME),
666 :user => self.encode_component(components[:user],
667 Addressable::URI::CharacterClasses::UNRESERVED),
668 :password => self.encode_component(components[:password],
669 Addressable::URI::CharacterClasses::UNRESERVED),
670 :host => components[:host],
671 :port => components[:port],
672 :path => self.encode_component(components[:path],
673 Addressable::URI::CharacterClasses::PATH),
674 :query => self.encode_component(components[:query],
675 Addressable::URI::CharacterClasses::QUERY),
676 :fragment => self.encode_component(components[:fragment],
677 Addressable::URI::CharacterClasses::FRAGMENT)
678 )
679 if return_type == String
680 return encoded_uri.to_s
681 elsif return_type == ::Addressable::URI
682 return encoded_uri
683 end
684 end
685
686 ##
687 # Encodes a set of key/value pairs according to the rules for the
688 # <code>application/x-www-form-urlencoded</code> MIME type.
689 #
690 # @param [#to_hash, #to_ary] form_values
691 # The form values to encode.
692 #
693 # @param [TrueClass, FalseClass] sort
694 # Sort the key/value pairs prior to encoding.
695 # Defaults to <code>false</code>.
696 #
697 # @return [String]
698 # The encoded value.
699 def self.form_encode(form_values, sort=false)
700 if form_values.respond_to?(:to_hash)
701 form_values = form_values.to_hash.to_a
702 elsif form_values.respond_to?(:to_ary)
703 form_values = form_values.to_ary
704 else
705 raise TypeError, "Can't convert #{form_values.class} into Array."
706 end
707
708 form_values = form_values.inject([]) do |accu, (key, value)|
709 if value.kind_of?(Array)
710 value.each do |v|
711 accu << [key.to_s, v.to_s]
712 end
713 else
714 accu << [key.to_s, value.to_s]
715 end
716 accu
717 end
718
719 if sort
720 # Useful for OAuth and optimizing caching systems
721 form_values = form_values.sort
722 end
723 escaped_form_values = form_values.map do |(key, value)|
724 # Line breaks are CRLF pairs
725 [
726 self.encode_component(
727 key.gsub(/(\r\n|\n|\r)/, "\r\n"),
728 CharacterClasses::UNRESERVED
729 ).gsub("%20", "+"),
730 self.encode_component(
731 value.gsub(/(\r\n|\n|\r)/, "\r\n"),
732 CharacterClasses::UNRESERVED
733 ).gsub("%20", "+")
734 ]
735 end
736 return escaped_form_values.map do |(key, value)|
737 "#{key}=#{value}"
738 end.join("&")
739 end
740
741 ##
742 # Decodes a <code>String</code> according to the rules for the
743 # <code>application/x-www-form-urlencoded</code> MIME type.
744 #
745 # @param [String, #to_str] encoded_value
746 # The form values to decode.
747 #
748 # @return [Array]
749 # The decoded values.
750 # This is not a <code>Hash</code> because of the possibility for
751 # duplicate keys.
752 def self.form_unencode(encoded_value)
753 if !encoded_value.respond_to?(:to_str)
754 raise TypeError, "Can't convert #{encoded_value.class} into String."
755 end
756 encoded_value = encoded_value.to_str
757 split_values = encoded_value.split("&").map do |pair|
758 pair.split("=", 2)
759 end
760 return split_values.map do |(key, value)|
761 [
762 key ? self.unencode_component(
763 key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil,
764 value ? (self.unencode_component(
765 value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil
766 ]
767 end
768 end
769
770 ##
771 # Creates a new uri object from component parts.
772 #
773 # @option [String, #to_str] scheme The scheme component.
774 # @option [String, #to_str] user The user component.
775 # @option [String, #to_str] password The password component.
776 # @option [String, #to_str] userinfo
777 # The userinfo component. If this is supplied, the user and password
778 # components must be omitted.
779 # @option [String, #to_str] host The host component.
780 # @option [String, #to_str] port The port component.
781 # @option [String, #to_str] authority
782 # The authority component. If this is supplied, the user, password,
783 # userinfo, host, and port components must be omitted.
784 # @option [String, #to_str] path The path component.
785 # @option [String, #to_str] query The query component.
786 # @option [String, #to_str] fragment The fragment component.
787 #
788 # @return [Addressable::URI] The constructed URI object.
789 def initialize(options={})
790 if options.has_key?(:authority)
791 if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
792 raise ArgumentError,
793 "Cannot specify both an authority and any of the components " +
794 "within the authority."
795 end
796 end
797 if options.has_key?(:userinfo)
798 if (options.keys & [:user, :password]).any?
799 raise ArgumentError,
800 "Cannot specify both a userinfo and either the user or password."
801 end
802 end
803
804 self.defer_validation do
805 # Bunch of crazy logic required because of the composite components
806 # like userinfo and authority.
807 self.scheme = options[:scheme] if options[:scheme]
808 self.user = options[:user] if options[:user]
809 self.password = options[:password] if options[:password]
810 self.userinfo = options[:userinfo] if options[:userinfo]
811 self.host = options[:host] if options[:host]
812 self.port = options[:port] if options[:port]
813 self.authority = options[:authority] if options[:authority]
814 self.path = options[:path] if options[:path]
815 self.query = options[:query] if options[:query]
816 self.query_values = options[:query_values] if options[:query_values]
817 self.fragment = options[:fragment] if options[:fragment]
818 end
819 self.to_s
820 end
821
822 ##
823 # Freeze URI, initializing instance variables.
824 #
825 # @return [Addressable::URI] The frozen URI object.
826 def freeze
827 self.normalized_scheme
828 self.normalized_user
829 self.normalized_password
830 self.normalized_userinfo
831 self.normalized_host
832 self.normalized_port
833 self.normalized_authority
834 self.normalized_site
835 self.normalized_path
836 self.normalized_query
837 self.normalized_fragment
838 self.hash
839 super
840 end
841
842 ##
843 # The scheme component for this URI.
844 #
845 # @return [String] The scheme component.
846 def scheme
847 return defined?(@scheme) ? @scheme : nil
848 end
849
850 ##
851 # The scheme component for this URI, normalized.
852 #
853 # @return [String] The scheme component, normalized.
854 def normalized_scheme
855 return nil unless self.scheme
856 @normalized_scheme ||= begin
857 if self.scheme =~ /^\s*ssh\+svn\s*$/i
858 "svn+ssh".dup
859 else
860 Addressable::URI.normalize_component(
861 self.scheme.strip.downcase,
862 Addressable::URI::CharacterClasses::SCHEME
863 )
864 end
865 end
866 # All normalized values should be UTF-8
867 @normalized_scheme.force_encoding(Encoding::UTF_8) if @normalized_scheme
868 @normalized_scheme
869 end
870
871 ##
872 # Sets the scheme component for this URI.
873 #
874 # @param [String, #to_str] new_scheme The new scheme component.
875 def scheme=(new_scheme)
876 if new_scheme && !new_scheme.respond_to?(:to_str)
877 raise TypeError, "Can't convert #{new_scheme.class} into String."
878 elsif new_scheme
879 new_scheme = new_scheme.to_str
880 end
881 if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
882 raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
883 end
884 @scheme = new_scheme
885 @scheme = nil if @scheme.to_s.strip.empty?
886
887 # Reset dependent values
888 remove_instance_variable(:@normalized_scheme) if defined?(@normalized_scheme)
889 remove_composite_values
890
891 # Ensure we haven't created an invalid URI
892 validate()
893 end
894
895 ##
896 # The user component for this URI.
897 #
898 # @return [String] The user component.
899 def user
900 return defined?(@user) ? @user : nil
901 end
902
903 ##
904 # The user component for this URI, normalized.
905 #
906 # @return [String] The user component, normalized.
907 def normalized_user
908 return nil unless self.user
909 return @normalized_user if defined?(@normalized_user)
910 @normalized_user ||= begin
911 if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
912 (!self.password || self.password.strip.empty?)
913 nil
914 else
915 Addressable::URI.normalize_component(
916 self.user.strip,
917 Addressable::URI::CharacterClasses::UNRESERVED
918 )
919 end
920 end
921 # All normalized values should be UTF-8
922 @normalized_user.force_encoding(Encoding::UTF_8) if @normalized_user
923 @normalized_user
924 end
925
926 ##
927 # Sets the user component for this URI.
928 #
929 # @param [String, #to_str] new_user The new user component.
930 def user=(new_user)
931 if new_user && !new_user.respond_to?(:to_str)
932 raise TypeError, "Can't convert #{new_user.class} into String."
933 end
934 @user = new_user ? new_user.to_str : nil
935
936 # You can't have a nil user with a non-nil password
937 if password != nil
938 @user = EMPTY_STR if @user.nil?
939 end
940
941 # Reset dependent values
942 remove_instance_variable(:@userinfo) if defined?(@userinfo)
943 remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
944 remove_instance_variable(:@authority) if defined?(@authority)
945 remove_instance_variable(:@normalized_user) if defined?(@normalized_user)
946 remove_composite_values
947
948 # Ensure we haven't created an invalid URI
949 validate()
950 end
951
952 ##
953 # The password component for this URI.
954 #
955 # @return [String] The password component.
956 def password
957 return defined?(@password) ? @password : nil
958 end
959
960 ##
961 # The password component for this URI, normalized.
962 #
963 # @return [String] The password component, normalized.
964 def normalized_password
965 return nil unless self.password
966 return @normalized_password if defined?(@normalized_password)
967 @normalized_password ||= begin
968 if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
969 (!self.user || self.user.strip.empty?)
970 nil
971 else
972 Addressable::URI.normalize_component(
973 self.password.strip,
974 Addressable::URI::CharacterClasses::UNRESERVED
975 )
976 end
977 end
978 # All normalized values should be UTF-8
979 if @normalized_password
980 @normalized_password.force_encoding(Encoding::UTF_8)
981 end
982 @normalized_password
983 end
984
985 ##
986 # Sets the password component for this URI.
987 #
988 # @param [String, #to_str] new_password The new password component.
989 def password=(new_password)
990 if new_password && !new_password.respond_to?(:to_str)
991 raise TypeError, "Can't convert #{new_password.class} into String."
992 end
993 @password = new_password ? new_password.to_str : nil
994
995 # You can't have a nil user with a non-nil password
996 @password ||= nil
997 @user ||= nil
998 if @password != nil
999 @user = EMPTY_STR if @user.nil?
1000 end
1001
1002 # Reset dependent values
1003 remove_instance_variable(:@userinfo) if defined?(@userinfo)
1004 remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1005 remove_instance_variable(:@authority) if defined?(@authority)
1006 remove_instance_variable(:@normalized_password) if defined?(@normalized_password)
1007 remove_composite_values
1008
1009 # Ensure we haven't created an invalid URI
1010 validate()
1011 end
1012
1013 ##
1014 # The userinfo component for this URI.
1015 # Combines the user and password components.
1016 #
1017 # @return [String] The userinfo component.
1018 def userinfo
1019 current_user = self.user
1020 current_password = self.password
1021 (current_user || current_password) && @userinfo ||= begin
1022 if current_user && current_password
1023 "#{current_user}:#{current_password}"
1024 elsif current_user && !current_password
1025 "#{current_user}"
1026 end
1027 end
1028 end
1029
1030 ##
1031 # The userinfo component for this URI, normalized.
1032 #
1033 # @return [String] The userinfo component, normalized.
1034 def normalized_userinfo
1035 return nil unless self.userinfo
1036 return @normalized_userinfo if defined?(@normalized_userinfo)
1037 @normalized_userinfo ||= begin
1038 current_user = self.normalized_user
1039 current_password = self.normalized_password
1040 if !current_user && !current_password
1041 nil
1042 elsif current_user && current_password
1043 "#{current_user}:#{current_password}".dup
1044 elsif current_user && !current_password
1045 "#{current_user}".dup
1046 end
1047 end
1048 # All normalized values should be UTF-8
1049 if @normalized_userinfo
1050 @normalized_userinfo.force_encoding(Encoding::UTF_8)
1051 end
1052 @normalized_userinfo
1053 end
1054
1055 ##
1056 # Sets the userinfo component for this URI.
1057 #
1058 # @param [String, #to_str] new_userinfo The new userinfo component.
1059 def userinfo=(new_userinfo)
1060 if new_userinfo && !new_userinfo.respond_to?(:to_str)
1061 raise TypeError, "Can't convert #{new_userinfo.class} into String."
1062 end
1063 new_user, new_password = if new_userinfo
1064 [
1065 new_userinfo.to_str.strip[/^(.*):/, 1],
1066 new_userinfo.to_str.strip[/:(.*)$/, 1]
1067 ]
1068 else
1069 [nil, nil]
1070 end
1071
1072 # Password assigned first to ensure validity in case of nil
1073 self.password = new_password
1074 self.user = new_user
1075
1076 # Reset dependent values
1077 remove_instance_variable(:@authority) if defined?(@authority)
1078 remove_composite_values
1079
1080 # Ensure we haven't created an invalid URI
1081 validate()
1082 end
1083
1084 ##
1085 # The host component for this URI.
1086 #
1087 # @return [String] The host component.
1088 def host
1089 return defined?(@host) ? @host : nil
1090 end
1091
1092 ##
1093 # The host component for this URI, normalized.
1094 #
1095 # @return [String] The host component, normalized.
1096 def normalized_host
1097 return nil unless self.host
1098 @normalized_host ||= begin
1099 if !self.host.strip.empty?
1100 result = ::Addressable::IDNA.to_ascii(
1101 URI.unencode_component(self.host.strip.downcase)
1102 )
1103 if result =~ /[^\.]\.$/
1104 # Single trailing dots are unnecessary.
1105 result = result[0...-1]
1106 end
1107 result = Addressable::URI.normalize_component(
1108 result,
1109 CharacterClasses::HOST)
1110 result
1111 else
1112 EMPTY_STR.dup
1113 end
1114 end
1115 # All normalized values should be UTF-8
1116 @normalized_host.force_encoding(Encoding::UTF_8) if @normalized_host
1117 @normalized_host
1118 end
1119
1120 ##
1121 # Sets the host component for this URI.
1122 #
1123 # @param [String, #to_str] new_host The new host component.
1124 def host=(new_host)
1125 if new_host && !new_host.respond_to?(:to_str)
1126 raise TypeError, "Can't convert #{new_host.class} into String."
1127 end
1128 @host = new_host ? new_host.to_str : nil
1129
1130 # Reset dependent values
1131 remove_instance_variable(:@authority) if defined?(@authority)
1132 remove_instance_variable(:@normalized_host) if defined?(@normalized_host)
1133 remove_composite_values
1134
1135 # Ensure we haven't created an invalid URI
1136 validate()
1137 end
1138
1139 ##
1140 # This method is same as URI::Generic#host except
1141 # brackets for IPv6 (and 'IPvFuture') addresses are removed.
1142 #
1143 # @see Addressable::URI#host
1144 #
1145 # @return [String] The hostname for this URI.
1146 def hostname
1147 v = self.host
1148 /\A\[(.*)\]\z/ =~ v ? $1 : v
1149 end
1150
1151 ##
1152 # This method is same as URI::Generic#host= except
1153 # the argument can be a bare IPv6 address (or 'IPvFuture').
1154 #
1155 # @see Addressable::URI#host=
1156 #
1157 # @param [String, #to_str] new_hostname The new hostname for this URI.
1158 def hostname=(new_hostname)
1159 if new_hostname &&
1160 (new_hostname.respond_to?(:ipv4?) || new_hostname.respond_to?(:ipv6?))
1161 new_hostname = new_hostname.to_s
1162 elsif new_hostname && !new_hostname.respond_to?(:to_str)
1163 raise TypeError, "Can't convert #{new_hostname.class} into String."
1164 end
1165 v = new_hostname ? new_hostname.to_str : nil
1166 v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
1167 self.host = v
1168 end
1169
1170 ##
1171 # Returns the top-level domain for this host.
1172 #
1173 # @example
1174 # Addressable::URI.parse("www.example.co.uk").tld # => "co.uk"
1175 def tld
1176 PublicSuffix.parse(self.host, ignore_private: true).tld
1177 end
1178
1179 ##
1180 # Sets the top-level domain for this URI.
1181 #
1182 # @param [String, #to_str] new_tld The new top-level domain.
1183 def tld=(new_tld)
1184 replaced_tld = domain.sub(/#{tld}\z/, new_tld)
1185 self.host = PublicSuffix::Domain.new(replaced_tld).to_s
1186 end
1187
1188 ##
1189 # Returns the public suffix domain for this host.
1190 #
1191 # @example
1192 # Addressable::URI.parse("www.example.co.uk").domain # => "example.co.uk"
1193 def domain
1194 PublicSuffix.domain(self.host, ignore_private: true)
1195 end
1196
1197 ##
1198 # The authority component for this URI.
1199 # Combines the user, password, host, and port components.
1200 #
1201 # @return [String] The authority component.
1202 def authority
1203 self.host && @authority ||= begin
1204 authority = String.new
1205 if self.userinfo != nil
1206 authority << "#{self.userinfo}@"
1207 end
1208 authority << self.host
1209 if self.port != nil
1210 authority << ":#{self.port}"
1211 end
1212 authority
1213 end
1214 end
1215
1216 ##
1217 # The authority component for this URI, normalized.
1218 #
1219 # @return [String] The authority component, normalized.
1220 def normalized_authority
1221 return nil unless self.authority
1222 @normalized_authority ||= begin
1223 authority = String.new
1224 if self.normalized_userinfo != nil
1225 authority << "#{self.normalized_userinfo}@"
1226 end
1227 authority << self.normalized_host
1228 if self.normalized_port != nil
1229 authority << ":#{self.normalized_port}"
1230 end
1231 authority
1232 end
1233 # All normalized values should be UTF-8
1234 if @normalized_authority
1235 @normalized_authority.force_encoding(Encoding::UTF_8)
1236 end
1237 @normalized_authority
1238 end
1239
1240 ##
1241 # Sets the authority component for this URI.
1242 #
1243 # @param [String, #to_str] new_authority The new authority component.
1244 def authority=(new_authority)
1245 if new_authority
1246 if !new_authority.respond_to?(:to_str)
1247 raise TypeError, "Can't convert #{new_authority.class} into String."
1248 end
1249 new_authority = new_authority.to_str
1250 new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
1251 if new_userinfo
1252 new_user = new_userinfo.strip[/^([^:]*):?/, 1]
1253 new_password = new_userinfo.strip[/:(.*)$/, 1]
1254 end
1255 new_host = new_authority.sub(
1256 /^([^\[\]]*)@/, EMPTY_STR
1257 ).sub(
1258 /:([^:@\[\]]*?)$/, EMPTY_STR
1259 )
1260 new_port =
1261 new_authority[/:([^:@\[\]]*?)$/, 1]
1262 end
1263
1264 # Password assigned first to ensure validity in case of nil
1265 self.password = defined?(new_password) ? new_password : nil
1266 self.user = defined?(new_user) ? new_user : nil
1267 self.host = defined?(new_host) ? new_host : nil
1268 self.port = defined?(new_port) ? new_port : nil
1269
1270 # Reset dependent values
1271 remove_instance_variable(:@userinfo) if defined?(@userinfo)
1272 remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1273 remove_composite_values
1274
1275 # Ensure we haven't created an invalid URI
1276 validate()
1277 end
1278
1279 ##
1280 # The origin for this URI, serialized to ASCII, as per
1281 # RFC 6454, section 6.2.
1282 #
1283 # @return [String] The serialized origin.
1284 def origin
1285 if self.scheme && self.authority
1286 if self.normalized_port
1287 "#{self.normalized_scheme}://#{self.normalized_host}" +
1288 ":#{self.normalized_port}"
1289 else
1290 "#{self.normalized_scheme}://#{self.normalized_host}"
1291 end
1292 else
1293 "null"
1294 end
1295 end
1296
1297 ##
1298 # Sets the origin for this URI, serialized to ASCII, as per
1299 # RFC 6454, section 6.2. This assignment will reset the `userinfo`
1300 # component.
1301 #
1302 # @param [String, #to_str] new_origin The new origin component.
1303 def origin=(new_origin)
1304 if new_origin
1305 if !new_origin.respond_to?(:to_str)
1306 raise TypeError, "Can't convert #{new_origin.class} into String."
1307 end
1308 new_origin = new_origin.to_str
1309 new_scheme = new_origin[/^([^:\/?#]+):\/\//, 1]
1310 unless new_scheme
1311 raise InvalidURIError, 'An origin cannot omit the scheme.'
1312 end
1313 new_host = new_origin[/:\/\/([^\/?#:]+)/, 1]
1314 unless new_host
1315 raise InvalidURIError, 'An origin cannot omit the host.'
1316 end
1317 new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
1318 end
1319
1320 self.scheme = defined?(new_scheme) ? new_scheme : nil
1321 self.host = defined?(new_host) ? new_host : nil
1322 self.port = defined?(new_port) ? new_port : nil
1323 self.userinfo = nil
1324
1325 # Reset dependent values
1326 remove_instance_variable(:@userinfo) if defined?(@userinfo)
1327 remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
1328 remove_instance_variable(:@authority) if defined?(@authority)
1329 remove_instance_variable(:@normalized_authority) if defined?(@normalized_authority)
1330 remove_composite_values
1331
1332 # Ensure we haven't created an invalid URI
1333 validate()
1334 end
1335
1336 # Returns an array of known ip-based schemes. These schemes typically
1337 # use a similar URI form:
1338 # <code>//<user>:<password>@<host>:<port>/<url-path></code>
1339 def self.ip_based_schemes
1340 return self.port_mapping.keys
1341 end
1342
1343 # Returns a hash of common IP-based schemes and their default port
1344 # numbers. Adding new schemes to this hash, as necessary, will allow
1345 # for better URI normalization.
1346 def self.port_mapping
1347 PORT_MAPPING
1348 end
1349
1350 ##
1351 # The port component for this URI.
1352 # This is the port number actually given in the URI. This does not
1353 # infer port numbers from default values.
1354 #
1355 # @return [Integer] The port component.
1356 def port
1357 return defined?(@port) ? @port : nil
1358 end
1359
1360 ##
1361 # The port component for this URI, normalized.
1362 #
1363 # @return [Integer] The port component, normalized.
1364 def normalized_port
1365 return nil unless self.port
1366 return @normalized_port if defined?(@normalized_port)
1367 @normalized_port ||= begin
1368 if URI.port_mapping[self.normalized_scheme] == self.port
1369 nil
1370 else
1371 self.port
1372 end
1373 end
1374 end
1375
1376 ##
1377 # Sets the port component for this URI.
1378 #
1379 # @param [String, Integer, #to_s] new_port The new port component.
1380 def port=(new_port)
1381 if new_port != nil && new_port.respond_to?(:to_str)
1382 new_port = Addressable::URI.unencode_component(new_port.to_str)
1383 end
1384
1385 if new_port.respond_to?(:valid_encoding?) && !new_port.valid_encoding?
1386 raise InvalidURIError, "Invalid encoding in port"
1387 end
1388
1389 if new_port != nil && !(new_port.to_s =~ /^\d+$/)
1390 raise InvalidURIError,
1391 "Invalid port number: #{new_port.inspect}"
1392 end
1393
1394 @port = new_port.to_s.to_i
1395 @port = nil if @port == 0
1396
1397 # Reset dependent values
1398 remove_instance_variable(:@authority) if defined?(@authority)
1399 remove_instance_variable(:@normalized_port) if defined?(@normalized_port)
1400 remove_composite_values
1401
1402 # Ensure we haven't created an invalid URI
1403 validate()
1404 end
1405
1406 ##
1407 # The inferred port component for this URI.
1408 # This method will normalize to the default port for the URI's scheme if
1409 # the port isn't explicitly specified in the URI.
1410 #
1411 # @return [Integer] The inferred port component.
1412 def inferred_port
1413 if self.port.to_i == 0
1414 self.default_port
1415 else
1416 self.port.to_i
1417 end
1418 end
1419
1420 ##
1421 # The default port for this URI's scheme.
1422 # This method will always returns the default port for the URI's scheme
1423 # regardless of the presence of an explicit port in the URI.
1424 #
1425 # @return [Integer] The default port.
1426 def default_port
1427 URI.port_mapping[self.scheme.strip.downcase] if self.scheme
1428 end
1429
1430 ##
1431 # The combination of components that represent a site.
1432 # Combines the scheme, user, password, host, and port components.
1433 # Primarily useful for HTTP and HTTPS.
1434 #
1435 # For example, <code>"http://example.com/path?query"</code> would have a
1436 # <code>site</code> value of <code>"http://example.com"</code>.
1437 #
1438 # @return [String] The components that identify a site.
1439 def site
1440 (self.scheme || self.authority) && @site ||= begin
1441 site_string = "".dup
1442 site_string << "#{self.scheme}:" if self.scheme != nil
1443 site_string << "//#{self.authority}" if self.authority != nil
1444 site_string
1445 end
1446 end
1447
1448 ##
1449 # The normalized combination of components that represent a site.
1450 # Combines the scheme, user, password, host, and port components.
1451 # Primarily useful for HTTP and HTTPS.
1452 #
1453 # For example, <code>"http://example.com/path?query"</code> would have a
1454 # <code>site</code> value of <code>"http://example.com"</code>.
1455 #
1456 # @return [String] The normalized components that identify a site.
1457 def normalized_site
1458 return nil unless self.site
1459 @normalized_site ||= begin
1460 site_string = "".dup
1461 if self.normalized_scheme != nil
1462 site_string << "#{self.normalized_scheme}:"
1463 end
1464 if self.normalized_authority != nil
1465 site_string << "//#{self.normalized_authority}"
1466 end
1467 site_string
1468 end
1469 # All normalized values should be UTF-8
1470 @normalized_site.force_encoding(Encoding::UTF_8) if @normalized_site
1471 @normalized_site
1472 end
1473
1474 ##
1475 # Sets the site value for this URI.
1476 #
1477 # @param [String, #to_str] new_site The new site value.
1478 def site=(new_site)
1479 if new_site
1480 if !new_site.respond_to?(:to_str)
1481 raise TypeError, "Can't convert #{new_site.class} into String."
1482 end
1483 new_site = new_site.to_str
1484 # These two regular expressions derived from the primary parsing
1485 # expression
1486 self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1]
1487 self.authority = new_site[
1488 /^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1
1489 ]
1490 else
1491 self.scheme = nil
1492 self.authority = nil
1493 end
1494 end
1495
1496 ##
1497 # The path component for this URI.
1498 #
1499 # @return [String] The path component.
1500 def path
1501 return defined?(@path) ? @path : EMPTY_STR
1502 end
1503
1504 NORMPATH = /^(?!\/)[^\/:]*:.*$/
1505 ##
1506 # The path component for this URI, normalized.
1507 #
1508 # @return [String] The path component, normalized.
1509 def normalized_path
1510 @normalized_path ||= begin
1511 path = self.path.to_s
1512 if self.scheme == nil && path =~ NORMPATH
1513 # Relative paths with colons in the first segment are ambiguous.
1514 path = path.sub(":", "%2F")
1515 end
1516 # String#split(delimeter, -1) uses the more strict splitting behavior
1517 # found by default in Python.
1518 result = path.strip.split(SLASH, -1).map do |segment|
1519 Addressable::URI.normalize_component(
1520 segment,
1521 Addressable::URI::CharacterClasses::PCHAR
1522 )
1523 end.join(SLASH)
1524
1525 result = URI.normalize_path(result)
1526 if result.empty? &&
1527 ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
1528 result = SLASH.dup
1529 end
1530 result
1531 end
1532 # All normalized values should be UTF-8
1533 @normalized_path.force_encoding(Encoding::UTF_8) if @normalized_path
1534 @normalized_path
1535 end
1536
1537 ##
1538 # Sets the path component for this URI.
1539 #
1540 # @param [String, #to_str] new_path The new path component.
1541 def path=(new_path)
1542 if new_path && !new_path.respond_to?(:to_str)
1543 raise TypeError, "Can't convert #{new_path.class} into String."
1544 end
1545 @path = (new_path || EMPTY_STR).to_str
1546 if !@path.empty? && @path[0..0] != SLASH && host != nil
1547 @path = "/#{@path}"
1548 end
1549
1550 # Reset dependent values
1551 remove_instance_variable(:@normalized_path) if defined?(@normalized_path)
1552 remove_composite_values
1553
1554 # Ensure we haven't created an invalid URI
1555 validate()
1556 end
1557
1558 ##
1559 # The basename, if any, of the file in the path component.
1560 #
1561 # @return [String] The path's basename.
1562 def basename
1563 # Path cannot be nil
1564 return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
1565 end
1566
1567 ##
1568 # The extname, if any, of the file in the path component.
1569 # Empty string if there is no extension.
1570 #
1571 # @return [String] The path's extname.
1572 def extname
1573 return nil unless self.path
1574 return File.extname(self.basename)
1575 end
1576
1577 ##
1578 # The query component for this URI.
1579 #
1580 # @return [String] The query component.
1581 def query
1582 return defined?(@query) ? @query : nil
1583 end
1584
1585 ##
1586 # The query component for this URI, normalized.
1587 #
1588 # @return [String] The query component, normalized.
1589 def normalized_query(*flags)
1590 return nil unless self.query
1591 return @normalized_query if defined?(@normalized_query)
1592 @normalized_query ||= begin
1593 modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1594 # Make sure possible key-value pair delimiters are escaped.
1595 modified_query_class.sub!("\\&", "").sub!("\\;", "")
1596 pairs = (self.query || "").split("&", -1)
1597 pairs.sort! if flags.include?(:sorted)
1598 component = pairs.map do |pair|
1599 Addressable::URI.normalize_component(pair, modified_query_class, "+")
1600 end.join("&")
1601 component == "" ? nil : component
1602 end
1603 # All normalized values should be UTF-8
1604 @normalized_query.force_encoding(Encoding::UTF_8) if @normalized_query
1605 @normalized_query
1606 end
1607
1608 ##
1609 # Sets the query component for this URI.
1610 #
1611 # @param [String, #to_str] new_query The new query component.
1612 def query=(new_query)
1613 if new_query && !new_query.respond_to?(:to_str)
1614 raise TypeError, "Can't convert #{new_query.class} into String."
1615 end
1616 @query = new_query ? new_query.to_str : nil
1617
1618 # Reset dependent values
1619 remove_instance_variable(:@normalized_query) if defined?(@normalized_query)
1620 remove_composite_values
1621 end
1622
1623 ##
1624 # Converts the query component to a Hash value.
1625 #
1626 # @param [Class] return_type The return type desired. Value must be either
1627 # `Hash` or `Array`.
1628 #
1629 # @return [Hash, Array, nil] The query string parsed as a Hash or Array
1630 # or nil if the query string is blank.
1631 #
1632 # @example
1633 # Addressable::URI.parse("?one=1&two=2&three=3").query_values
1634 # #=> {"one" => "1", "two" => "2", "three" => "3"}
1635 # Addressable::URI.parse("?one=two&one=three").query_values(Array)
1636 # #=> [["one", "two"], ["one", "three"]]
1637 # Addressable::URI.parse("?one=two&one=three").query_values(Hash)
1638 # #=> {"one" => "three"}
1639 # Addressable::URI.parse("?").query_values
1640 # #=> {}
1641 # Addressable::URI.parse("").query_values
1642 # #=> nil
1643 def query_values(return_type=Hash)
1644 empty_accumulator = Array == return_type ? [] : {}
1645 if return_type != Hash && return_type != Array
1646 raise ArgumentError, "Invalid return type. Must be Hash or Array."
1647 end
1648 return nil if self.query == nil
1649 split_query = self.query.split("&").map do |pair|
1650 pair.split("=", 2) if pair && !pair.empty?
1651 end.compact
1652 return split_query.inject(empty_accumulator.dup) do |accu, pair|
1653 # I'd rather use key/value identifiers instead of array lookups,
1654 # but in this case I really want to maintain the exact pair structure,
1655 # so it's best to make all changes in-place.
1656 pair[0] = URI.unencode_component(pair[0])
1657 if pair[1].respond_to?(:to_str)
1658 # I loathe the fact that I have to do this. Stupid HTML 4.01.
1659 # Treating '+' as a space was just an unbelievably bad idea.
1660 # There was nothing wrong with '%20'!
1661 # If it ain't broke, don't fix it!
1662 pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
1663 end
1664 if return_type == Hash
1665 accu[pair[0]] = pair[1]
1666 else
1667 accu << pair
1668 end
1669 accu
1670 end
1671 end
1672
1673 ##
1674 # Sets the query component for this URI from a Hash object.
1675 # An empty Hash or Array will result in an empty query string.
1676 #
1677 # @param [Hash, #to_hash, Array] new_query_values The new query values.
1678 #
1679 # @example
1680 # uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
1681 # uri.query
1682 # # => "a=a&b=c&b=d&b=e"
1683 # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
1684 # uri.query
1685 # # => "a=a&b=c&b=d&b=e"
1686 # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
1687 # uri.query
1688 # # => "a=a&b=c&b=d&b=e"
1689 # uri.query_values = [['flag'], ['key', 'value']]
1690 # uri.query
1691 # # => "flag&key=value"
1692 def query_values=(new_query_values)
1693 if new_query_values == nil
1694 self.query = nil
1695 return nil
1696 end
1697
1698 if !new_query_values.is_a?(Array)
1699 if !new_query_values.respond_to?(:to_hash)
1700 raise TypeError,
1701 "Can't convert #{new_query_values.class} into Hash."
1702 end
1703 new_query_values = new_query_values.to_hash
1704 new_query_values = new_query_values.map do |key, value|
1705 key = key.to_s if key.kind_of?(Symbol)
1706 [key, value]
1707 end
1708 # Useful default for OAuth and caching.
1709 # Only to be used for non-Array inputs. Arrays should preserve order.
1710 new_query_values.sort!
1711 end
1712
1713 # new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
1714 buffer = "".dup
1715 new_query_values.each do |key, value|
1716 encoded_key = URI.encode_component(
1717 key, CharacterClasses::UNRESERVED
1718 )
1719 if value == nil
1720 buffer << "#{encoded_key}&"
1721 elsif value.kind_of?(Array)
1722 value.each do |sub_value|
1723 encoded_value = URI.encode_component(
1724 sub_value, CharacterClasses::UNRESERVED
1725 )
1726 buffer << "#{encoded_key}=#{encoded_value}&"
1727 end
1728 else
1729 encoded_value = URI.encode_component(
1730 value, CharacterClasses::UNRESERVED
1731 )
1732 buffer << "#{encoded_key}=#{encoded_value}&"
1733 end
1734 end
1735 self.query = buffer.chop
1736 end
1737
1738 ##
1739 # The HTTP request URI for this URI. This is the path and the
1740 # query string.
1741 #
1742 # @return [String] The request URI required for an HTTP request.
1743 def request_uri
1744 return nil if self.absolute? && self.scheme !~ /^https?$/i
1745 return (
1746 (!self.path.empty? ? self.path : SLASH) +
1747 (self.query ? "?#{self.query}" : EMPTY_STR)
1748 )
1749 end
1750
1751 ##
1752 # Sets the HTTP request URI for this URI.
1753 #
1754 # @param [String, #to_str] new_request_uri The new HTTP request URI.
1755 def request_uri=(new_request_uri)
1756 if !new_request_uri.respond_to?(:to_str)
1757 raise TypeError, "Can't convert #{new_request_uri.class} into String."
1758 end
1759 if self.absolute? && self.scheme !~ /^https?$/i
1760 raise InvalidURIError,
1761 "Cannot set an HTTP request URI for a non-HTTP URI."
1762 end
1763 new_request_uri = new_request_uri.to_str
1764 path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
1765 query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
1766 path_component = path_component.to_s
1767 path_component = (!path_component.empty? ? path_component : SLASH)
1768 self.path = path_component
1769 self.query = query_component
1770
1771 # Reset dependent values
1772 remove_composite_values
1773 end
1774
1775 ##
1776 # The fragment component for this URI.
1777 #
1778 # @return [String] The fragment component.
1779 def fragment
1780 return defined?(@fragment) ? @fragment : nil
1781 end
1782
1783 ##
1784 # The fragment component for this URI, normalized.
1785 #
1786 # @return [String] The fragment component, normalized.
1787 def normalized_fragment
1788 return nil unless self.fragment
1789 return @normalized_fragment if defined?(@normalized_fragment)
1790 @normalized_fragment ||= begin
1791 component = Addressable::URI.normalize_component(
1792 self.fragment,
1793 Addressable::URI::CharacterClasses::FRAGMENT
1794 )
1795 component == "" ? nil : component
1796 end
1797 # All normalized values should be UTF-8
1798 if @normalized_fragment
1799 @normalized_fragment.force_encoding(Encoding::UTF_8)
1800 end
1801 @normalized_fragment
1802 end
1803
1804 ##
1805 # Sets the fragment component for this URI.
1806 #
1807 # @param [String, #to_str] new_fragment The new fragment component.
1808 def fragment=(new_fragment)
1809 if new_fragment && !new_fragment.respond_to?(:to_str)
1810 raise TypeError, "Can't convert #{new_fragment.class} into String."
1811 end
1812 @fragment = new_fragment ? new_fragment.to_str : nil
1813
1814 # Reset dependent values
1815 remove_instance_variable(:@normalized_fragment) if defined?(@normalized_fragment)
1816 remove_composite_values
1817
1818 # Ensure we haven't created an invalid URI
1819 validate()
1820 end
1821
1822 ##
1823 # Determines if the scheme indicates an IP-based protocol.
1824 #
1825 # @return [TrueClass, FalseClass]
1826 # <code>true</code> if the scheme indicates an IP-based protocol.
1827 # <code>false</code> otherwise.
1828 def ip_based?
1829 if self.scheme
1830 return URI.ip_based_schemes.include?(
1831 self.scheme.strip.downcase)
1832 end
1833 return false
1834 end
1835
1836 ##
1837 # Determines if the URI is relative.
1838 #
1839 # @return [TrueClass, FalseClass]
1840 # <code>true</code> if the URI is relative. <code>false</code>
1841 # otherwise.
1842 def relative?
1843 return self.scheme.nil?
1844 end
1845
1846 ##
1847 # Determines if the URI is absolute.
1848 #
1849 # @return [TrueClass, FalseClass]
1850 # <code>true</code> if the URI is absolute. <code>false</code>
1851 # otherwise.
1852 def absolute?
1853 return !relative?
1854 end
1855
1856 ##
1857 # Joins two URIs together.
1858 #
1859 # @param [String, Addressable::URI, #to_str] The URI to join with.
1860 #
1861 # @return [Addressable::URI] The joined URI.
1862 def join(uri)
1863 if !uri.respond_to?(:to_str)
1864 raise TypeError, "Can't convert #{uri.class} into String."
1865 end
1866 if !uri.kind_of?(URI)
1867 # Otherwise, convert to a String, then parse.
1868 uri = URI.parse(uri.to_str)
1869 end
1870 if uri.to_s.empty?
1871 return self.dup
1872 end
1873
1874 joined_scheme = nil
1875 joined_user = nil
1876 joined_password = nil
1877 joined_host = nil
1878 joined_port = nil
1879 joined_path = nil
1880 joined_query = nil
1881 joined_fragment = nil
1882
1883 # Section 5.2.2 of RFC 3986
1884 if uri.scheme != nil
1885 joined_scheme = uri.scheme
1886 joined_user = uri.user
1887 joined_password = uri.password
1888 joined_host = uri.host
1889 joined_port = uri.port
1890 joined_path = URI.normalize_path(uri.path)
1891 joined_query = uri.query
1892 else
1893 if uri.authority != nil
1894 joined_user = uri.user
1895 joined_password = uri.password
1896 joined_host = uri.host
1897 joined_port = uri.port
1898 joined_path = URI.normalize_path(uri.path)
1899 joined_query = uri.query
1900 else
1901 if uri.path == nil || uri.path.empty?
1902 joined_path = self.path
1903 if uri.query != nil
1904 joined_query = uri.query
1905 else
1906 joined_query = self.query
1907 end
1908 else
1909 if uri.path[0..0] == SLASH
1910 joined_path = URI.normalize_path(uri.path)
1911 else
1912 base_path = self.path.dup
1913 base_path = EMPTY_STR if base_path == nil
1914 base_path = URI.normalize_path(base_path)
1915
1916 # Section 5.2.3 of RFC 3986
1917 #
1918 # Removes the right-most path segment from the base path.
1919 if base_path =~ /\//
1920 base_path.sub!(/\/[^\/]+$/, SLASH)
1921 else
1922 base_path = EMPTY_STR
1923 end
1924
1925 # If the base path is empty and an authority segment has been
1926 # defined, use a base path of SLASH
1927 if base_path.empty? && self.authority != nil
1928 base_path = SLASH
1929 end
1930
1931 joined_path = URI.normalize_path(base_path + uri.path)
1932 end
1933 joined_query = uri.query
1934 end
1935 joined_user = self.user
1936 joined_password = self.password
1937 joined_host = self.host
1938 joined_port = self.port
1939 end
1940 joined_scheme = self.scheme
1941 end
1942 joined_fragment = uri.fragment
1943
1944 return self.class.new(
1945 :scheme => joined_scheme,
1946 :user => joined_user,
1947 :password => joined_password,
1948 :host => joined_host,
1949 :port => joined_port,
1950 :path => joined_path,
1951 :query => joined_query,
1952 :fragment => joined_fragment
1953 )
1954 end
1955 alias_method :+, :join
1956
1957 ##
1958 # Destructive form of <code>join</code>.
1959 #
1960 # @param [String, Addressable::URI, #to_str] The URI to join with.
1961 #
1962 # @return [Addressable::URI] The joined URI.
1963 #
1964 # @see Addressable::URI#join
1965 def join!(uri)
1966 replace_self(self.join(uri))
1967 end
1968
1969 ##
1970 # Merges a URI with a <code>Hash</code> of components.
1971 # This method has different behavior from <code>join</code>. Any
1972 # components present in the <code>hash</code> parameter will override the
1973 # original components. The path component is not treated specially.
1974 #
1975 # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1976 #
1977 # @return [Addressable::URI] The merged URI.
1978 #
1979 # @see Hash#merge
1980 def merge(hash)
1981 if !hash.respond_to?(:to_hash)
1982 raise TypeError, "Can't convert #{hash.class} into Hash."
1983 end
1984 hash = hash.to_hash
1985
1986 if hash.has_key?(:authority)
1987 if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
1988 raise ArgumentError,
1989 "Cannot specify both an authority and any of the components " +
1990 "within the authority."
1991 end
1992 end
1993 if hash.has_key?(:userinfo)
1994 if (hash.keys & [:user, :password]).any?
1995 raise ArgumentError,
1996 "Cannot specify both a userinfo and either the user or password."
1997 end
1998 end
1999
2000 uri = self.class.new
2001 uri.defer_validation do
2002 # Bunch of crazy logic required because of the composite components
2003 # like userinfo and authority.
2004 uri.scheme =
2005 hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
2006 if hash.has_key?(:authority)
2007 uri.authority =
2008 hash.has_key?(:authority) ? hash[:authority] : self.authority
2009 end
2010 if hash.has_key?(:userinfo)
2011 uri.userinfo =
2012 hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
2013 end
2014 if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
2015 uri.user =
2016 hash.has_key?(:user) ? hash[:user] : self.user
2017 uri.password =
2018 hash.has_key?(:password) ? hash[:password] : self.password
2019 end
2020 if !hash.has_key?(:authority)
2021 uri.host =
2022 hash.has_key?(:host) ? hash[:host] : self.host
2023 uri.port =
2024 hash.has_key?(:port) ? hash[:port] : self.port
2025 end
2026 uri.path =
2027 hash.has_key?(:path) ? hash[:path] : self.path
2028 uri.query =
2029 hash.has_key?(:query) ? hash[:query] : self.query
2030 uri.fragment =
2031 hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
2032 end
2033
2034 return uri
2035 end
2036
2037 ##
2038 # Destructive form of <code>merge</code>.
2039 #
2040 # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
2041 #
2042 # @return [Addressable::URI] The merged URI.
2043 #
2044 # @see Addressable::URI#merge
2045 def merge!(uri)
2046 replace_self(self.merge(uri))
2047 end
2048
2049 ##
2050 # Returns the shortest normalized relative form of this URI that uses the
2051 # supplied URI as a base for resolution. Returns an absolute URI if
2052 # necessary. This is effectively the opposite of <code>route_to</code>.
2053 #
2054 # @param [String, Addressable::URI, #to_str] uri The URI to route from.
2055 #
2056 # @return [Addressable::URI]
2057 # The normalized relative URI that is equivalent to the original URI.
2058 def route_from(uri)
2059 uri = URI.parse(uri).normalize
2060 normalized_self = self.normalize
2061 if normalized_self.relative?
2062 raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
2063 end
2064 if uri.relative?
2065 raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
2066 end
2067 if normalized_self == uri
2068 return Addressable::URI.parse("##{normalized_self.fragment}")
2069 end
2070 components = normalized_self.to_hash
2071 if normalized_self.scheme == uri.scheme
2072 components[:scheme] = nil
2073 if normalized_self.authority == uri.authority
2074 components[:user] = nil
2075 components[:password] = nil
2076 components[:host] = nil
2077 components[:port] = nil
2078 if normalized_self.path == uri.path
2079 components[:path] = nil
2080 if normalized_self.query == uri.query
2081 components[:query] = nil
2082 end
2083 else
2084 if uri.path != SLASH and components[:path]
2085 self_splitted_path = split_path(components[:path])
2086 uri_splitted_path = split_path(uri.path)
2087 self_dir = self_splitted_path.shift
2088 uri_dir = uri_splitted_path.shift
2089 while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
2090 self_dir = self_splitted_path.shift
2091 uri_dir = uri_splitted_path.shift
2092 end
2093 components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
2094 end
2095 end
2096 end
2097 end
2098 # Avoid network-path references.
2099 if components[:host] != nil
2100 components[:scheme] = normalized_self.scheme
2101 end
2102 return Addressable::URI.new(
2103 :scheme => components[:scheme],
2104 :user => components[:user],
2105 :password => components[:password],
2106 :host => components[:host],
2107 :port => components[:port],
2108 :path => components[:path],
2109 :query => components[:query],
2110 :fragment => components[:fragment]
2111 )
2112 end
2113
2114 ##
2115 # Returns the shortest normalized relative form of the supplied URI that
2116 # uses this URI as a base for resolution. Returns an absolute URI if
2117 # necessary. This is effectively the opposite of <code>route_from</code>.
2118 #
2119 # @param [String, Addressable::URI, #to_str] uri The URI to route to.
2120 #
2121 # @return [Addressable::URI]
2122 # The normalized relative URI that is equivalent to the supplied URI.
2123 def route_to(uri)
2124 return URI.parse(uri).route_from(self)
2125 end
2126
2127 ##
2128 # Returns a normalized URI object.
2129 #
2130 # NOTE: This method does not attempt to fully conform to specifications.
2131 # It exists largely to correct other people's failures to read the
2132 # specifications, and also to deal with caching issues since several
2133 # different URIs may represent the same resource and should not be
2134 # cached multiple times.
2135 #
2136 # @return [Addressable::URI] The normalized URI.
2137 def normalize
2138 # This is a special exception for the frequently misused feed
2139 # URI scheme.
2140 if normalized_scheme == "feed"
2141 if self.to_s =~ /^feed:\/*http:\/*/
2142 return URI.parse(
2143 self.to_s[/^feed:\/*(http:\/*.*)/, 1]
2144 ).normalize
2145 end
2146 end
2147
2148 return self.class.new(
2149 :scheme => normalized_scheme,
2150 :authority => normalized_authority,
2151 :path => normalized_path,
2152 :query => normalized_query,
2153 :fragment => normalized_fragment
2154 )
2155 end
2156
2157 ##
2158 # Destructively normalizes this URI object.
2159 #
2160 # @return [Addressable::URI] The normalized URI.
2161 #
2162 # @see Addressable::URI#normalize
2163 def normalize!
2164 replace_self(self.normalize)
2165 end
2166
2167 ##
2168 # Creates a URI suitable for display to users. If semantic attacks are
2169 # likely, the application should try to detect these and warn the user.
2170 # See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
2171 # section 7.6 for more information.
2172 #
2173 # @return [Addressable::URI] A URI suitable for display purposes.
2174 def display_uri
2175 display_uri = self.normalize
2176 display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
2177 return display_uri
2178 end
2179
2180 ##
2181 # Returns <code>true</code> if the URI objects are equal. This method
2182 # normalizes both URIs before doing the comparison, and allows comparison
2183 # against <code>Strings</code>.
2184 #
2185 # @param [Object] uri The URI to compare.
2186 #
2187 # @return [TrueClass, FalseClass]
2188 # <code>true</code> if the URIs are equivalent, <code>false</code>
2189 # otherwise.
2190 def ===(uri)
2191 if uri.respond_to?(:normalize)
2192 uri_string = uri.normalize.to_s
2193 else
2194 begin
2195 uri_string = ::Addressable::URI.parse(uri).normalize.to_s
2196 rescue InvalidURIError, TypeError
2197 return false
2198 end
2199 end
2200 return self.normalize.to_s == uri_string
2201 end
2202
2203 ##
2204 # Returns <code>true</code> if the URI objects are equal. This method
2205 # normalizes both URIs before doing the comparison.
2206 #
2207 # @param [Object] uri The URI to compare.
2208 #
2209 # @return [TrueClass, FalseClass]
2210 # <code>true</code> if the URIs are equivalent, <code>false</code>
2211 # otherwise.
2212 def ==(uri)
2213 return false unless uri.kind_of?(URI)
2214 return self.normalize.to_s == uri.normalize.to_s
2215 end
2216
2217 ##
2218 # Returns <code>true</code> if the URI objects are equal. This method
2219 # does NOT normalize either URI before doing the comparison.
2220 #
2221 # @param [Object] uri The URI to compare.
2222 #
2223 # @return [TrueClass, FalseClass]
2224 # <code>true</code> if the URIs are equivalent, <code>false</code>
2225 # otherwise.
2226 def eql?(uri)
2227 return false unless uri.kind_of?(URI)
2228 return self.to_s == uri.to_s
2229 end
2230
2231 ##
2232 # A hash value that will make a URI equivalent to its normalized
2233 # form.
2234 #
2235 # @return [Integer] A hash of the URI.
2236 def hash
2237 @hash ||= self.to_s.hash * -1
2238 end
2239
2240 ##
2241 # Clones the URI object.
2242 #
2243 # @return [Addressable::URI] The cloned URI.
2244 def dup
2245 duplicated_uri = self.class.new(
2246 :scheme => self.scheme ? self.scheme.dup : nil,
2247 :user => self.user ? self.user.dup : nil,
2248 :password => self.password ? self.password.dup : nil,
2249 :host => self.host ? self.host.dup : nil,
2250 :port => self.port,
2251 :path => self.path ? self.path.dup : nil,
2252 :query => self.query ? self.query.dup : nil,
2253 :fragment => self.fragment ? self.fragment.dup : nil
2254 )
2255 return duplicated_uri
2256 end
2257
2258 ##
2259 # Omits components from a URI.
2260 #
2261 # @param [Symbol] *components The components to be omitted.
2262 #
2263 # @return [Addressable::URI] The URI with components omitted.
2264 #
2265 # @example
2266 # uri = Addressable::URI.parse("http://example.com/path?query")
2267 # #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
2268 # uri.omit(:scheme, :authority)
2269 # #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
2270 def omit(*components)
2271 invalid_components = components - [
2272 :scheme, :user, :password, :userinfo, :host, :port, :authority,
2273 :path, :query, :fragment
2274 ]
2275 unless invalid_components.empty?
2276 raise ArgumentError,
2277 "Invalid component names: #{invalid_components.inspect}."
2278 end
2279 duplicated_uri = self.dup
2280 duplicated_uri.defer_validation do
2281 components.each do |component|
2282 duplicated_uri.send((component.to_s + "=").to_sym, nil)
2283 end
2284 duplicated_uri.user = duplicated_uri.normalized_user
2285 end
2286 duplicated_uri
2287 end
2288
2289 ##
2290 # Destructive form of omit.
2291 #
2292 # @param [Symbol] *components The components to be omitted.
2293 #
2294 # @return [Addressable::URI] The URI with components omitted.
2295 #
2296 # @see Addressable::URI#omit
2297 def omit!(*components)
2298 replace_self(self.omit(*components))
2299 end
2300
2301 ##
2302 # Determines if the URI is an empty string.
2303 #
2304 # @return [TrueClass, FalseClass]
2305 # Returns <code>true</code> if empty, <code>false</code> otherwise.
2306 def empty?
2307 return self.to_s.empty?
2308 end
2309
2310 ##
2311 # Converts the URI to a <code>String</code>.
2312 #
2313 # @return [String] The URI's <code>String</code> representation.
2314 def to_s
2315 if self.scheme == nil && self.path != nil && !self.path.empty? &&
2316 self.path =~ NORMPATH
2317 raise InvalidURIError,
2318 "Cannot assemble URI string with ambiguous path: '#{self.path}'"
2319 end
2320 @uri_string ||= begin
2321 uri_string = String.new
2322 uri_string << "#{self.scheme}:" if self.scheme != nil
2323 uri_string << "//#{self.authority}" if self.authority != nil
2324 uri_string << self.path.to_s
2325 uri_string << "?#{self.query}" if self.query != nil
2326 uri_string << "##{self.fragment}" if self.fragment != nil
2327 uri_string.force_encoding(Encoding::UTF_8)
2328 uri_string
2329 end
2330 end
2331
2332 ##
2333 # URI's are glorified <code>Strings</code>. Allow implicit conversion.
2334 alias_method :to_str, :to_s
2335
2336 ##
2337 # Returns a Hash of the URI components.
2338 #
2339 # @return [Hash] The URI as a <code>Hash</code> of components.
2340 def to_hash
2341 return {
2342 :scheme => self.scheme,
2343 :user => self.user,
2344 :password => self.password,
2345 :host => self.host,
2346 :port => self.port,
2347 :path => self.path,
2348 :query => self.query,
2349 :fragment => self.fragment
2350 }
2351 end
2352
2353 ##
2354 # Returns a <code>String</code> representation of the URI object's state.
2355 #
2356 # @return [String] The URI object's state, as a <code>String</code>.
2357 def inspect
2358 sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
2359 end
2360
2361 ##
2362 # This method allows you to make several changes to a URI simultaneously,
2363 # which separately would cause validation errors, but in conjunction,
2364 # are valid. The URI will be revalidated as soon as the entire block has
2365 # been executed.
2366 #
2367 # @param [Proc] block
2368 # A set of operations to perform on a given URI.
2369 def defer_validation(&block)
2370 raise LocalJumpError, "No block given." unless block
2371 @validation_deferred = true
2372 block.call()
2373 @validation_deferred = false
2374 validate
2375 return nil
2376 end
2377
2378 protected
2379 SELF_REF = '.'
2380 PARENT = '..'
2381
2382 RULE_2A = /\/\.\/|\/\.$/
2383 RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
2384 RULE_2D = /^\.\.?\/?/
2385 RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
2386
2387 ##
2388 # Resolves paths to their simplest form.
2389 #
2390 # @param [String] path The path to normalize.
2391 #
2392 # @return [String] The normalized path.
2393 def self.normalize_path(path)
2394 # Section 5.2.4 of RFC 3986
2395
2396 return nil if path.nil?
2397 normalized_path = path.dup
2398 begin
2399 mod = nil
2400 mod ||= normalized_path.gsub!(RULE_2A, SLASH)
2401
2402 pair = normalized_path.match(RULE_2B_2C)
2403 parent, current = pair[1], pair[2] if pair
2404 if pair && ((parent != SELF_REF && parent != PARENT) ||
2405 (current != SELF_REF && current != PARENT))
2406 mod ||= normalized_path.gsub!(
2407 Regexp.new(
2408 "/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
2409 "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
2410 ), SLASH
2411 )
2412 end
2413
2414 mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
2415 # Non-standard, removes prefixed dotted segments from path.
2416 mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
2417 end until mod.nil?
2418
2419 return normalized_path
2420 end
2421
2422 ##
2423 # Ensures that the URI is valid.
2424 def validate
2425 return if !!@validation_deferred
2426 if self.scheme != nil && self.ip_based? &&
2427 (self.host == nil || self.host.empty?) &&
2428 (self.path == nil || self.path.empty?)
2429 raise InvalidURIError,
2430 "Absolute URI missing hierarchical segment: '#{self.to_s}'"
2431 end
2432 if self.host == nil
2433 if self.port != nil ||
2434 self.user != nil ||
2435 self.password != nil
2436 raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
2437 end
2438 end
2439 if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
2440 self.authority != nil
2441 raise InvalidURIError,
2442 "Cannot have a relative path with an authority set: '#{self.to_s}'"
2443 end
2444 if self.path != nil && !self.path.empty? &&
2445 self.path[0..1] == SLASH + SLASH && self.authority == nil
2446 raise InvalidURIError,
2447 "Cannot have a path with two leading slashes " +
2448 "without an authority set: '#{self.to_s}'"
2449 end
2450 unreserved = CharacterClasses::UNRESERVED
2451 sub_delims = CharacterClasses::SUB_DELIMS
2452 if !self.host.nil? && (self.host =~ /[<>{}\/\\\?\#\@"[[:space:]]]/ ||
2453 (self.host[/^\[(.*)\]$/, 1] != nil && self.host[/^\[(.*)\]$/, 1] !~
2454 Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
2455 raise InvalidURIError, "Invalid character in host: '#{self.host.to_s}'"
2456 end
2457 return nil
2458 end
2459
2460 ##
2461 # Replaces the internal state of self with the specified URI's state.
2462 # Used in destructive operations to avoid massive code repetition.
2463 #
2464 # @param [Addressable::URI] uri The URI to replace <code>self</code> with.
2465 #
2466 # @return [Addressable::URI] <code>self</code>.
2467 def replace_self(uri)
2468 # Reset dependent values
2469 instance_variables.each do |var|
2470 if instance_variable_defined?(var) && var != :@validation_deferred
2471 remove_instance_variable(var)
2472 end
2473 end
2474
2475 @scheme = uri.scheme
2476 @user = uri.user
2477 @password = uri.password
2478 @host = uri.host
2479 @port = uri.port
2480 @path = uri.path
2481 @query = uri.query
2482 @fragment = uri.fragment
2483 return self
2484 end
2485
2486 ##
2487 # Splits path string with "/" (slash).
2488 # It is considered that there is empty string after last slash when
2489 # path ends with slash.
2490 #
2491 # @param [String] path The path to split.
2492 #
2493 # @return [Array<String>] An array of parts of path.
2494 def split_path(path)
2495 splitted = path.split(SLASH)
2496 splitted << EMPTY_STR if path.end_with? SLASH
2497 splitted
2498 end
2499
2500 ##
2501 # Resets composite values for the entire URI
2502 #
2503 # @api private
2504 def remove_composite_values
2505 remove_instance_variable(:@uri_string) if defined?(@uri_string)
2506 remove_instance_variable(:@hash) if defined?(@hash)
2507 end
2508 end
2509 end
+0
-32
vendor/addressable/version.rb less more
0 # frozen_string_literal: true
1
2 # encoding:utf-8
3 #--
4 # Copyright (C) Bob Aman
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #++
18
19
20 # Used to prevent the class/module from being loaded more than once
21 if !defined?(Addressable::VERSION)
22 module Addressable
23 module VERSION
24 MAJOR = 2
25 MINOR = 6
26 TINY = 0
27
28 STRING = [MAJOR, MINOR, TINY].join('.')
29 end
30 end
31 end
+0
-4
vendor/addressable.rb less more
0 # frozen_string_literal: true
1
2 require 'addressable/uri'
3 require 'addressable/template'
+0
-43
vendor/delayer/deferred/chain/await.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/chain/base"
2
3 module Delayer::Deferred::Chain
4 class Await < Base
5 def initialize(worker:, deferred:)
6 super()
7 @worker, @awaiting_deferred = worker, deferred
8 deferred.add_awaited(self)
9 end
10
11 def activate(response)
12 change_sequence(:activate)
13 @worker.give_response(response, @awaiting_deferred)
14 # TODO: 即座にspoilさせてよさそう
15 ensure
16 change_sequence(:complete)
17 end
18
19 def graph_child(output:)
20 output << graph_mynode
21 if has_child?
22 @child.graph_child(output: output)
23 @awaiting_deferred.graph_child(output: output)
24 output << "#{__id__} -> #{@child.__id__}"
25 end
26 nil
27 end
28
29 def node_name
30 "Await"
31 end
32
33 def graph_shape
34 'circle'.freeze
35 end
36
37 def graph_mynode
38 label = "#{node_name}\n(#{sequence.name})"
39 "#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
40 end
41 end
42 end
+0
-35
vendor/delayer/deferred/chain/base.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/deferredable/chainable"
2 require "delayer/deferred/deferredable/node_sequence"
3
4 module Delayer::Deferred::Chain
5 class Base
6 include Delayer::Deferred::Deferredable::NodeSequence
7 include Delayer::Deferred::Deferredable::Chainable
8
9 def initialize(&proc)
10 fail Error, "Delayer::Deferred::Chain can't create instance." if self.class == Delayer::Deferred::Chain::Base
11 @proc = proc
12 end
13
14 def activate(response)
15 change_sequence(:activate)
16 if evaluate?(response)
17 @proc.(response.value)
18 else
19 response
20 end
21 ensure
22 change_sequence(:complete)
23 end
24
25 def inspect
26 "#<#{self.class} seq:#{sequence.name} child:#{has_child?}>"
27 end
28
29 def node_name
30 @proc.source_location.join(':'.freeze)
31 end
32 end
33 end
34
+0
-16
vendor/delayer/deferred/chain/next.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/chain/base"
2
3 module Delayer::Deferred::Chain
4 class Next < Base
5 def evaluate?(response)
6 response.ok?
7 end
8
9 private
10
11 def graph_shape
12 'box'.freeze
13 end
14 end
15 end
+0
-16
vendor/delayer/deferred/chain/trap.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/chain/base"
2
3 module Delayer::Deferred::Chain
4 class Trap < Base
5 def evaluate?(response)
6 response.ng?
7 end
8
9 private
10
11 def graph_shape
12 'diamond'.freeze
13 end
14 end
15 end
+0
-10
vendor/delayer/deferred/chain.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer::Deferred
3 module Chain; end
4 end
5
6 require "delayer/deferred/chain/await"
7 require "delayer/deferred/chain/base"
8 require "delayer/deferred/chain/next"
9 require "delayer/deferred/chain/trap"
+0
-10
vendor/delayer/deferred/deferred.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/promise"
2 require "delayer/deferred/chain"
3 require "delayer/deferred/deferredable"
4 require "delayer/deferred/worker"
5 require "delayer/deferred/version"
6
7 module Delayer::Deferred
8 Deferred = Promise
9 end
+0
-27
vendor/delayer/deferred/deferredable/awaitable.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer::Deferred::Deferredable
3 module Awaitable
4
5 # _self_ が終了して結果が出るまで呼び出し側のDeferredを停止し、 _self_ の結果を返す。
6 # 呼び出し側はDeferredブロック内でなければならないが、 _Deferred#next_ を使わずに
7 # 直接戻り値を得ることが出来る。
8 # _self_ が失敗した場合は、呼び出し側のDeferredの直近の _trap_ ブロックが呼ばれる。
9 def +@
10 response = Fiber.yield(Delayer::Deferred::Request::Await.new(self))
11 if response.ok?
12 response.value
13 else
14 Delayer::Deferred.fail(response.value)
15 end
16 end
17
18 def enter_await
19 change_sequence(:await)
20 end
21
22 def exit_await
23 change_sequence(:resume)
24 end
25 end
26 end
+0
-155
vendor/delayer/deferred/deferredable/chainable.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/deferredable/awaitable"
2 require "delayer/deferred/deferredable/graph"
3 require "delayer/deferred/deferredable/node_sequence"
4
5 module Delayer::Deferred::Deferredable
6 module Chainable
7 include Awaitable
8 include Graph
9 include NodeSequence
10
11 attr_reader :child
12
13 # このDeferredが成功した場合の処理を追加する。
14 # 新しいDeferredのインスタンスを返す。
15 # このメソッドはスレッドセーフです。
16 # TODO: procが空のとき例外を発生させる
17 def next(&proc)
18 add_child(Delayer::Deferred::Chain::Next.new(&proc))
19 end
20 alias deferred next
21
22 # このDeferredが失敗した場合の処理を追加する。
23 # 新しいDeferredのインスタンスを返す。
24 # このメソッドはスレッドセーフです。
25 # TODO: procが空のとき例外を発生させる
26 def trap(&proc)
27 add_child(Delayer::Deferred::Chain::Trap.new(&proc))
28 end
29 alias error trap
30
31 # この一連のDeferredをこれ以上実行しない。
32 # このメソッドはスレッドセーフです。
33 def cancel
34 change_sequence(:genocide) unless spoiled?
35 end
36
37 def has_child?
38 child ? true : false
39 end
40
41 # 子を追加する。
42 # _Delayer::Deferred::Chainable_ を直接指定できる。通常外部から呼ぶときは _next_ か _trap_ メソッドを使うこと。
43 # このメソッドはスレッドセーフです。
44 # ==== Args
45 # [chainable] 子となるDeferred
46 # ==== Return
47 # 必ず _chainable_ を返す
48 # ==== Raise
49 # [Delayer::Deferred::SequenceError]
50 # 既に子が存在している場合
51 def add_child(chainable)
52 change_sequence(:get_child) do
53 chainable.parent = self
54 @child = chainable
55 end
56 end
57
58 # 子が追加された時に一度だけコールバックするオブジェクトを登録する。
59 # observerと言っているが、実際には _Delayer::Deferred::Worker_ を渡して利用している。
60 # このメソッドはスレッドセーフです。
61 # ==== Args
62 # [observer] pushメソッドを備えているもの。引数に _@child_ の値が渡される
63 # ==== Return
64 # self
65 def add_child_observer(observer)
66 change_sequence(:gaze) do
67 @child_observer = observer
68 end
69 self
70 end
71
72 def awaited
73 @awaited ||= [].freeze
74 end
75
76 def has_awaited?
77 not awaited.empty?
78 end
79
80 def add_awaited(awaitable)
81 @awaited = [*awaited, awaitable].freeze
82 self
83 end
84
85 # activateメソッドを呼ぶDelayerジョブを登録する寸前に呼ばれる。
86 def reserve_activate
87 change_sequence(:reserve)
88 end
89
90 def enter_pass
91 change_sequence(:pass)
92 end
93
94 def exit_pass
95 change_sequence(:resume)
96 end
97
98 protected
99
100 # 親を再帰的に辿り、一番最初のノードを返す。
101 # 親が複数見つかった場合は、それらを返す。
102 def ancestor
103 if @parent
104 @parent.ancestor
105 else
106 self
107 end
108 end
109
110 # cancelとかデバッグ用のコールグラフを得るために親を登録しておく。
111 # add_childから呼ばれる。
112 def parent=(chainable)
113 @parent = chainable
114 end
115
116 private
117
118 def call_child_observer
119 if has_child? and defined?(@child_observer)
120 change_sequence(:called)
121 @child_observer.push(@child)
122 end
123 end
124
125 def on_sequence_changed(old_seq, flow, new_seq)
126 case new_seq
127 when NodeSequence::BURST_OUT
128 call_child_observer
129 when NodeSequence::GENOCIDE
130 @parent.cancel if defined?(@parent) and @parent
131 when NodeSequence::RESERVED_C, NodeSequence::RUN_C, NodeSequence::PASS_C, NodeSequence::AWAIT_C, NodeSequence::GRAFT_C
132 if !has_child?
133 notice "child: #{@child.inspect}"
134 raise Delayer::Deferred::SequenceError.new("Sequence changed `#{old_seq.name}' to `#{flow}', but it has no child")
135 end
136 end
137 end
138
139 # ノードの名前。サブクラスでオーバライドし、ノードが定義されたファイルの名前や行数などを入れておく。
140 def node_name
141 self.class.to_s
142 end
143
144 def graph_mynode
145 if defined?(@seq_logger)
146 label = "#{node_name}\n(#{@seq_logger.map(&:name).join('→')})"
147 else
148 label = "#{node_name}\n(#{sequence.name})"
149 end
150 "#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
151 end
152
153 end
154 end
+0
-118
vendor/delayer/deferred/deferredable/graph.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer::Deferred::Deferredable
3 =begin rdoc
4 graphvizによってChainableなDeferredをDOT言語形式でダンプする機能を追加するmix-in。
5 いずれかのノードに対して _graph_ メソッドを呼ぶと、再帰的に親子を全て辿り、digraphの断片の文字列を得ることが出来る。
6
7 == 出力例
8
9 20892180 [shape=egg,label="#<Class:0x000000027da288>.Promise\n(reserved)"]
10 20892480 [shape=box,label="test/thread_test.rb:53\n(connected)"]
11 20891440 [shape=diamond,label="test/thread_test.rb:56\n(fresh)"]
12 20892480 -> 20891440
13 20892180 -> 20892480
14
15 =end
16 module Graph
17 # この一連のDeferredチェインの様子を、DOT言語フォーマットで出力する
18 # ==== Args
19 # [child_only:]
20 # _true_ なら、このノードとその子孫のみを描画する。
21 # _false_ なら、再帰的に親を遡り、そこから描画を開始する。
22 # [output:]
23 # このオブジェクトに、 _<<_ メソッドで内容が書かれる。
24 # 省略した場合は、戻り値が _String_ になる。
25 # ==== Return
26 # [String] DOT言語によるグラフ
27 # [output:] 引数 output: に指定されたオブジェクト
28 def graph(child_only: false, output: String.new)
29 if child_only
30 output << "digraph Deferred {\n".freeze
31 Enumerator.new{ |yielder|
32 graph_child(output: yielder)
33 }.lazy.each{|l|
34 output << "\t#{l}\n"
35 }
36 output << '}'.freeze
37 else
38 ancestor.graph(child_only: true, output: output)
39 end
40 end
41
42 # Graph.graph の結果を内容とする一時ファイルを作成して返す。
43 # ただし、ブロックを渡された場合は、一時ファイルを引数にそのブロックを一度だけ実行し、ブロックの戻り値をこのメソッドの戻り値とする。
44 # ==== Args
45 # [&block] 一時ファイルを利用する処理
46 # ==== Return
47 # [Tempfile] ブロックを指定しなかった場合。作成された一時ファイルオブジェクト
48 # [Object] ブロックが指定された場合。ブロックの実行結果。
49 def graph_save(permanent: false, &block)
50 if block
51 Tempfile.open{|tmp|
52 graph(output: tmp)
53 tmp.seek(0)
54 block.(tmp)
55 }
56 else
57 tmp = Tempfile.open
58 graph(output: tmp).tap{|t|t.seek(0)}
59 end
60 end
61
62 # 画像ファイルとしてグラフを書き出す。
63 # dotコマンドが使えないと失敗する。
64 # ==== Args
65 # [format:] 画像の拡張子
66 # ==== Return
67 # [String] 書き出したファイル名
68 def graph_draw(dir: '/tmp', format: 'svg'.freeze)
69 graph_save do |dotfile|
70 base = File.basename(dotfile.path)
71 dest = File.join(dir, "#{base}.#{format}")
72 system("dot -T#{format} #{dotfile.path} -o #{dest}")
73 dest
74 end
75 end
76
77 # このノードとその子全てのDeferredチェインの様子を、DOT言語フォーマットで出力する。
78 # Delayer::Deferred::Deferredable::Graph#graph の内部で利用されるため、将来このメソッドのインターフェイスは変更される可能性がある。
79 def graph_child(output:)
80 output << graph_mynode
81 if has_child?
82 @child.graph_child(output: output)
83 output << "#{__id__} -> #{@child.__id__}"
84 end
85 if has_awaited?
86 awaited.each do |awaitable|
87 if awaitable.is_a?(Delayer::Deferred::Deferredable::Chainable)
88 awaitable.ancestor.graph_child(output: output)
89 else
90 label = "#{awaitable.class}"
91 output << "#{awaitable.__id__} [shape=oval,label=#{label.inspect}]"
92 end
93 output << "#{awaitable.__id__} -> #{__id__} [label = \"await\", style = \"dotted\"]"
94 end
95 end
96 nil
97 end
98
99 private
100
101 # このノードを描画する時の形の名前を文字列で返す。
102 # 以下のページにあるような、graphvizで取り扱える形の中から選ぶこと。
103 # http://www.graphviz.org/doc/info/shapes.html
104 def graph_shape
105 'oval'.freeze
106 end
107
108 # このノードの形などをDOT言語の断片で返す。
109 # このメソッドをオーバライドすることで、描画されるノードの見た目を自由に変更することが出来る。
110 # ただし、簡単な変更だけなら別のメソッドをオーバライドするだけで可能なので、このmix-inの他のメソッドも参照すること。
111 def graph_mynode
112 label = "#{node_name}\n(#{sequence.name})"
113 "#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
114 end
115
116 end
117 end
+0
-158
vendor/delayer/deferred/deferredable/node_sequence.rb less more
0 # -*- coding: utf-8 -*-
1 require 'delayer/deferred/error'
2
3 require 'thread'
4
5 module Delayer::Deferred::Deferredable
6 module NodeSequence
7 class Sequence
8 attr_reader :name
9
10 def initialize(name)
11 @name = name.to_sym
12 @map = {}
13 @exceptions = Hash.new(Delayer::Deferred::SequenceError)
14 end
15
16 def add(seq, flow = seq.name)
17 @map[flow] = seq
18 self
19 end
20
21 def exception(exc, flow)
22 @exceptions[flow] = exc
23 self
24 end
25
26 def pull(flow)
27 if @map.has_key?(flow.to_sym)
28 @map[flow.to_sym]
29 else
30 raise @exceptions[flow.to_sym], "Invalid sequence flow `#{name}' to `#{flow}'."
31 end
32 end
33
34 def inspect
35 "#<#{self.class}: #{name}>"
36 end
37 end
38
39 FRESH = Sequence.new(:fresh)
40 CONNECTED = Sequence.new(:connected) # 子がいる、未実行
41 RESERVED = Sequence.new(:reserved) # 実行キュー待ち
42 RESERVED_C= Sequence.new(:reserved) # 実行キュー待ち(子がいる)
43 RUN = Sequence.new(:run) # 実行中
44 RUN_C = Sequence.new(:run) # 実行中(子がいる)
45 PASS = Sequence.new(:pass) # パス中
46 PASS_C = Sequence.new(:pass) # パス中
47 AWAIT = Sequence.new(:await) # Await中
48 AWAIT_C = Sequence.new(:await) # Await中(子がいる)
49 GRAFT = Sequence.new(:graft) # 戻り値がAwaitableの時
50 GRAFT_C = Sequence.new(:graft) # 戻り値がAwaitableの時(子がいる)
51 CALL_CHILD= Sequence.new(:call_child) # 完了、子がいる
52 STOP = Sequence.new(:stop) # 完了、子なし
53 WAIT = Sequence.new(:wait) # 完了、オブザーバ登録済み
54 BURST_OUT = Sequence.new(:burst_out) # 完了、オブザーバ登録済み、子追加済み
55 ROTTEN = Sequence.new(:rotten).freeze # 終了
56 GENOCIDE = Sequence.new(:genocide).freeze# この地ではかつて大量虐殺があったという。
57
58 FRESH
59 .add(CONNECTED, :get_child)
60 .add(RESERVED, :reserve)
61 .add(GENOCIDE).freeze
62 CONNECTED
63 .add(RESERVED_C, :reserve)
64 .exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
65 .add(GENOCIDE).freeze
66 RESERVED
67 .add(RUN, :activate)
68 .add(RESERVED_C, :get_child)
69 .add(GENOCIDE).freeze
70 RESERVED_C
71 .add(RUN_C, :activate)
72 .exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
73 .add(GENOCIDE).freeze
74 RUN
75 .add(RUN_C, :get_child)
76 .add(PASS)
77 .add(AWAIT, :await)
78 .add(STOP, :complete)
79 .add(GENOCIDE).freeze
80 RUN_C
81 .add(PASS_C)
82 .add(AWAIT_C, :await)
83 .add(CALL_CHILD, :complete)
84 .exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
85 .add(GENOCIDE).freeze
86 PASS
87 .add(PASS_C, :get_child)
88 .add(RUN, :resume)
89 .add(GENOCIDE).freeze
90 PASS_C
91 .add(RUN_C, :resume)
92 .add(GENOCIDE).freeze
93 AWAIT
94 .add(RUN, :resume)
95 .add(AWAIT_C, :get_child)
96 .add(GENOCIDE).freeze
97 AWAIT_C
98 .add(RUN_C, :resume)
99 .exception(Delayer::Deferred::MultipleAssignmentError, :get_child)
100 .add(GENOCIDE).freeze
101 CALL_CHILD
102 .add(GRAFT_C, :await)
103 .add(ROTTEN, :called)
104 .add(GENOCIDE).freeze
105 GRAFT
106 .add(STOP, :resume)
107 .add(GRAFT_C, :get_child)
108 .add(GENOCIDE).freeze
109 GRAFT_C
110 .add(CALL_CHILD, :resume)
111 .add(GENOCIDE).freeze
112 STOP
113 .add(GRAFT, :await)
114 .add(WAIT, :gaze)
115 .add(GENOCIDE).freeze
116 WAIT
117 .add(BURST_OUT, :get_child)
118 .add(GENOCIDE).freeze
119 BURST_OUT
120 .add(ROTTEN, :called)
121 .add(GENOCIDE).freeze
122
123 SEQUENCE_LOCK = Monitor.new
124
125 def sequence
126 @sequence ||= FRESH
127 end
128
129 # このメソッドはスレッドセーフです
130 def change_sequence(flow, &block)
131 SEQUENCE_LOCK.synchronize do
132 old_seq = sequence
133 new_seq = @sequence = sequence.pull(flow)
134 (@seq_logger ||= [old_seq]) << new_seq
135 if block
136 result = block.()
137 on_sequence_changed(old_seq, flow, new_seq)
138 result
139 else
140 on_sequence_changed(old_seq, flow, new_seq)
141 nil
142 end
143 end
144 end
145
146 def on_sequence_changed(old_seq, flow, new_seq)
147 end
148
149 def activated?
150 ![FRESH, CONNECTED, RUN, RUN_C].include?(sequence)
151 end
152
153 def spoiled?
154 sequence == ROTTEN || sequence == GENOCIDE
155 end
156 end
157 end
+0
-34
vendor/delayer/deferred/deferredable/trigger.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/deferredable/chainable"
2 require "delayer/deferred/deferredable/node_sequence"
3 require "delayer/deferred/response"
4
5 module Delayer::Deferred::Deferredable
6 =begin rdoc
7 Promiseなど、親を持たず、自身がWorkerを作成できるもの。
8 =end
9 module Trigger
10 include NodeSequence
11 include Chainable
12
13 # Deferredを直ちに実行する。
14 # このメソッドはスレッドセーフです。
15 def call(value = nil)
16 execute(Delayer::Deferred::Response::Ok.new(value))
17 end
18
19 # Deferredを直ちに失敗させる。
20 # このメソッドはスレッドセーフです。
21 def fail(exception = nil)
22 execute(Delayer::Deferred::Response::Ng.new(exception))
23 end
24
25 private
26
27 def execute(value)
28 worker = Delayer::Deferred::Worker.new(delayer: self.class.delayer,
29 initial: value)
30 worker.push(self)
31 end
32 end
33 end
+0
-12
vendor/delayer/deferred/deferredable.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/version"
2
3 module Delayer::Deferred
4 module Deferredable; end
5 end
6
7 require "delayer/deferred/deferredable/awaitable"
8 require "delayer/deferred/deferredable/chainable"
9 require "delayer/deferred/deferredable/graph"
10 require "delayer/deferred/deferredable/node_sequence"
11 require "delayer/deferred/deferredable/trigger"
+0
-10
vendor/delayer/deferred/enumerable.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer"
2 require "delayer/deferred/enumerator"
3
4 module Enumerable
5 # 遅延each。あとで実行されるし、あんまりループに時間がかかるようなら一旦ループを終了する
6 def deach(delayer=Delayer, &proc)
7 to_enum.deach(delayer, &proc)
8 end
9 end
+0
-14
vendor/delayer/deferred/enumerator.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer"
2 require "delayer/deferred/deferred"
3
4 class Enumerator
5 def deach(delayer=Delayer, &proc)
6 delayer.Deferred.new.next do
7 self.each do |node|
8 delayer.Deferred.pass
9 proc.(node)
10 end
11 end
12 end
13 end
+0
-22
vendor/delayer/deferred/error.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer::Deferred
3 Error = Class.new(StandardError)
4
5 class ForeignCommandAborted < Error
6 attr_reader :process
7 def initialize(message, process:)
8 super(message)
9 @process = process
10 end
11 end
12
13 SequenceError = Class.new(Error) do
14 attr_accessor :deferred
15 def initialize(message, deferred: nil)
16 super(message)
17 @deferred = deferred
18 end
19 end
20 MultipleAssignmentError = Class.new(SequenceError)
21 end
+0
-78
vendor/delayer/deferred/promise.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/tools"
2 require "delayer/deferred/deferredable/trigger"
3
4 module Delayer::Deferred
5 class Promise
6 extend Delayer::Deferred::Tools
7 include Deferredable::Trigger
8
9 class << self
10 def new(stop=false, name: caller_locations(1,1).first.to_s, &block)
11 result = promise = super(name: name)
12 result = promise.next(&block) if block_given?
13 promise.call(true) unless stop
14 result
15 end
16
17 def Thread
18 @thread_class ||= gen_thread_class end
19
20 def Promise
21 self
22 end
23
24 def delayer
25 ::Delayer
26 end
27
28 def to_s
29 "#{self.delayer}.Promise"
30 end
31
32 private
33
34 def gen_thread_class
35 the_delayer = delayer
36 Class.new(Thread) do
37 define_singleton_method(:delayer) do
38 the_delayer
39 end
40 end
41 end
42 end
43
44 def initialize(name:)
45 super()
46 @name = name
47 end
48
49 def activate(response)
50 change_sequence(:activate)
51 change_sequence(:complete)
52 response
53 end
54
55 def inspect
56 "#<#{self.class} seq:#{sequence.name}>"
57 end
58
59 def ancestor
60 self
61 end
62
63 def parent=(chainable)
64 fail Error, "#{self.class} can't has parent."
65 end
66
67 private
68
69 def graph_shape
70 'egg'.freeze
71 end
72
73 def node_name
74 @name.to_s
75 end
76 end
77 end
+0
-61
vendor/delayer/deferred/request.rb less more
0 # -*- coding: utf-8 -*-
1
2 # -*- coding: utf-8 -*-
3
4 module Delayer::Deferred::Request
5 class Base
6 attr_reader :value
7 def initialize(value)
8 @value = value
9 end
10 end
11
12 =begin rdoc
13 Fiberが次のWorkerを要求している時に返す値。
14 新たなインスタンスは作らず、 _NEXT_WORKER_ にあるインスタンスを使うこと。
15 =end
16 class NextWorker < Base
17 # _deferred_ に渡された次のChainableに、 _deferred_ の戻り値を渡す要求を出す。
18 # ==== Args
19 # [deferred] 実行が完了したDeferred 。次のDeferredとして _deferred.child_ を呼び出す
20 # [worker] このDeferredチェインを実行しているWorker
21 def accept_request(worker:, deferred:)
22 if deferred.has_child?
23 worker.push(deferred.child)
24 else
25 deferred.add_child_observer(worker)
26 end
27 end
28 end
29
30 =begin rdoc
31 Chainable#+@ が呼ばれた時に、一旦そこで処理を止めるためのリクエスト。
32 _value_ には、実行完了を待つDeferredが入っている。
33 ==== わかりやすい!
34 accept_requestメソッドの引数のdeferred {
35 +value
36 }
37 =end
38 class Await < Base
39 alias_method :foreign_deferred, :value
40 def accept_request(worker:, deferred:)
41 deferred.enter_await
42 foreign_deferred.add_child(Delayer::Deferred::Chain::Await.new(worker: worker, deferred: deferred))
43 end
44 end
45
46 =begin rdoc
47 一旦処理を中断して、Delayerキューに並び直すためのリクエスト。
48 Tools#pass から利用される。
49 新たなインスタンスは作らず、 _PASS_ にあるインスタンスを使うこと。
50 =end
51 class Pass < Base
52 def accept_request(worker:, deferred:)
53 deferred.enter_pass
54 worker.resume_pass(deferred)
55 end
56 end
57
58 NEXT_WORKER = NextWorker.new(nil).freeze
59 PASS = Pass.new(nil).freeze
60 end
+0
-26
vendor/delayer/deferred/response.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer::Deferred::Response
3 class Base
4 attr_reader :value
5 def initialize(value)
6 @value = value
7 end
8
9 def ng?
10 !ok?
11 end
12 end
13
14 class Ok < Base
15 def ok?
16 true
17 end
18 end
19
20 class Ng < Base
21 def ok?
22 false
23 end
24 end
25 end
+0
-11
vendor/delayer/deferred/result_container.rb less more
0 # -*- coding: utf-8 -*-
1
2 Delayer::Deferred::ResultContainer = Struct.new(:success_flag, :value) do
3 def ok?
4 success_flag
5 end
6
7 def ng?
8 !success_flag
9 end
10 end
+0
-57
vendor/delayer/deferred/thread.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer"
2 require "delayer/deferred/deferredable/awaitable"
3
4 class Thread
5 include ::Delayer::Deferred::Deferredable::Awaitable
6
7 def self.delayer
8 Delayer
9 end
10
11 # このDeferredが成功した場合の処理を追加する。
12 # 新しいDeferredのインスタンスを返す。
13 # このメソッドはスレッドセーフです。
14 # TODO: procが空のとき例外を発生させる
15 def next(name: caller_locations(1,1).first.to_s, &proc)
16 add_child(Delayer::Deferred::Chain::Next.new(&proc), name: name)
17 end
18 alias deferred next
19
20 # このDeferredが失敗した場合の処理を追加する。
21 # 新しいDeferredのインスタンスを返す。
22 # このメソッドはスレッドセーフです。
23 # TODO: procが空のとき例外を発生させる
24 def trap(name: caller_locations(1,1).first.to_s, &proc)
25 add_child(Delayer::Deferred::Chain::Trap.new(&proc), name: name)
26 end
27 alias error trap
28
29 def add_child(chainable, name: caller_locations(1,1).first.to_s)
30 __gen_promise(name).add_child(chainable)
31 end
32
33 private
34
35 def __gen_promise(name)
36 promise = self.class.delayer.Promise.new(true, name: name)
37 Thread.new(self) do |tt|
38 __promise_callback(tt, promise)
39 end
40 promise
41 end
42
43 def __promise_callback(tt, promise)
44 begin
45 result = tt.value
46 self.class.delayer.new do
47 promise.call(result)
48 end
49 rescue Exception => err
50 self.class.delayer.new do
51 promise.fail(err)
52 end
53 end
54 end
55
56 end
+0
-68
vendor/delayer/deferred/tools.rb less more
0 # -*- coding: utf-8 -*-
1 require 'delayer/deferred/error'
2
3 module Delayer::Deferred
4 module Tools
5 def next(&proc)
6 new.next(&proc) end
7
8 def trap(&proc)
9 new.trap(&proc) end
10
11 # 実行中のDeferredを失敗させる。raiseと違って、Exception以外のオブジェクトをtrap()に渡すことができる。
12 # Deferredのnextとtrapの中でだけ呼び出すことができる。
13 # ==== Args
14 # [value] trap()に渡す値
15 # ==== Throw
16 # :__deferredable_fail をthrowする
17 def fail(value)
18 throw(:__deferredable_fail, value) end
19
20 # 実行中のDeferredを、Delayerのタイムリミットが来ている場合に限り一旦中断する。
21 # 長期に渡る可能性のある処理で、必要に応じて他のタスクを先に実行してもよい場合に呼び出す。
22 def pass
23 Fiber.yield(Request::PASS) if delayer.expire?
24 end
25
26 # 複数のdeferredを引数に取って、それら全ての実行が終了したら、
27 # その結果を引数の順番通りに格納したArrayを引数に呼ばれるDeferredを返す。
28 # 引数のDeferredが一つでも失敗するとこのメソッドの返すDeferredも失敗する。
29 # ==== Args
30 # [*args] 終了を待つDeferredオブジェクト
31 # ==== Return
32 # Deferred
33 def when(*args)
34 args = args.flatten
35 return self.next{ [].freeze } if args.empty?
36 args.each_with_index{|d, index|
37 unless d.is_a?(Deferredable::Chainable) || d.is_a?(Deferredable::Awaitable)
38 raise TypeError, "Argument #{index} of Deferred.when must be #{Deferredable::Chainable}, but given #{d.class}"
39 end
40 if d.respond_to?(:has_child?) && d.has_child?
41 raise "Already assigned child for argument #{index}"
42 end
43 }
44 defer, *follow = *args
45 defer.next{|res|
46 [res, *follow.map{|d| +d }]
47 }
48 end
49
50 # Kernel#systemを呼び出して、コマンドが成功たら成功するDeferredを返す。
51 # 失敗した場合、trap{}ブロックには $? の値(Process::Status)か、例外が発生した場合それが渡される
52 # ==== Args
53 # [*args] Kernel#spawn の引数
54 # ==== Return
55 # Deferred
56 def system(*args)
57 delayer.Deferred.Thread.new {
58 Process.waitpid2(Kernel.spawn(*args))
59 }.next{|_pid, status|
60 if status && status.success?
61 status
62 else
63 raise ForeignCommandAborted.new("command aborted: #{args.join(' ')}", process: status) end
64 }
65 end
66 end
67 end
+0
-5
vendor/delayer/deferred/version.rb less more
0 module Delayer
1 module Deferred
2 VERSION = "2.1.1"
3 end
4 end
+0
-112
vendor/delayer/deferred/worker.rb less more
0 # -*- coding: utf-8 -*-
1 require "delayer/deferred/request"
2 require "delayer/deferred/response"
3
4 module Delayer::Deferred
5 =begin rdoc
6 Deferredを実行するためのWorker。Deferredチェインを実行するFiberを
7 管理する。
8
9 == pushに渡すオブジェクトについて
10 Worker#push に渡す引数は、activateメソッドを実装している必要がある。
11
12 === activate(response)
13 ==== Args
14 response :: Delayer::Deferred::Response::Base Deferredに渡す値
15 ==== Returns
16 [Delayer::Deferred::Response::Base]
17 これを返すと、値の自動変換が行われないため、意図的に失敗させたり、Deferredを次のブロックに伝搬させることができる。
18 [Delayer::Deferred::Chainable]
19 戻り値のDeferredが終わるまでWorkerの処理を停止する。
20 再開された時、結果は戻り値のDeferredの結果に置き換えられる。
21 [else]
22 _Delayer::Deferred::Response::Ok.new_ の引数に渡され、その結果が利用される
23 =end
24 class Worker
25 def initialize(delayer:, initial:)
26 @delayer, @initial = delayer, initial
27 end
28
29 def push(deferred)
30 deferred.reserve_activate
31 @delayer.new do
32 next if deferred.spoiled?
33 begin
34 fiber.resume(deferred).accept_request(worker: self,
35 deferred: deferred)
36 rescue Delayer::Deferred::SequenceError => err
37 err.deferred = deferred
38 raise
39 end
40 end
41 nil
42 end
43
44 # Awaitから復帰した時に呼ばれる。
45 # ==== Args
46 # [response] Awaitの結果(Delayer::Deferred::Response::Base)
47 # [deferred] 現在実行中のDeferred
48 def give_response(response, deferred)
49 @delayer.new do
50 next if deferred.spoiled?
51 deferred.exit_await
52 fiber.resume(response).accept_request(worker: self,
53 deferred: deferred)
54 end
55 nil
56 end
57
58 # Tools#pass から復帰した時に呼ばれる。
59 # ==== Args
60 # [deferred] 現在実行中のDeferred
61 def resume_pass(deferred)
62 deferred.exit_pass
63 @delayer.new do
64 next if deferred.spoiled?
65 fiber.resume(nil).accept_request(worker: self,
66 deferred: deferred)
67 end
68 end
69
70 private
71
72 def fiber
73 @fiber ||= Fiber.new{|response|
74 loop do
75 response = wait_and_activate(response)
76 case response.value
77 when Delayer::Deferred::SequenceError
78 raise response.value
79 end
80 end
81 }.tap{|f| f.resume(@initial); @initial = nil }
82 end
83
84 def wait_and_activate(argument)
85 response = catch(:success) do
86 failed = catch(:__deferredable_fail) do
87 begin
88 if argument.value.is_a? Deferredable::Awaitable
89 throw :success, +argument.value
90 else
91 defer = Fiber.yield(Request::NEXT_WORKER)
92 res = defer.activate(argument)
93 if res.is_a? Delayer::Deferred::Deferredable::Awaitable
94 defer.add_awaited(res)
95 end
96 end
97 throw :success, res
98 rescue Exception => err
99 throw :__deferredable_fail, err
100 end
101 end
102 Response::Ng.new(failed)
103 end
104 if response.is_a?(Response::Base)
105 response
106 else
107 Response::Ok.new(response)
108 end
109 end
110 end
111 end
+0
-44
vendor/delayer/deferred.rb less more
0 # coding: utf-8
1 require "delayer"
2 require "delayer/deferred/deferred"
3 require "delayer/deferred/deferredable"
4 require "delayer/deferred/enumerable"
5 require "delayer/deferred/enumerator"
6 require "delayer/deferred/thread"
7 require "delayer/deferred/tools"
8 require "delayer/deferred/version"
9
10 module Delayer
11 module Deferred
12 class << self
13 #真ならデバッグ情報を集める
14 attr_accessor :debug
15
16 def new(*rest, name: caller_locations(1,1).first.to_s, &block)
17 super(*rest, name: name, &block)
18 end
19
20 def method_missing(*rest, &block)
21 Delayer::Deferred::Promise.__send__(*rest, &block)
22 end
23
24 def respond_to_missing?(symbol, include_private)
25 Delayer::Deferred::Promise.respond_to?(symbol, include_private)
26 end
27 end
28 end
29
30 module Extend
31 def Promise
32 @promise ||= begin
33 the_delayer = self
34 Class.new(::Delayer::Deferred::Promise) {
35 define_singleton_method(:delayer) {
36 the_delayer } } end
37 end
38 alias :Deferred :Promise
39 #deprecate :Deferred, "Promise", 2018, 03
40 end
41 end
42
43 Delayer::Deferred.debug = false
+0
-25
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
10 class RecursiveError < Error; end
11 class NoLowerLevelError < RecursiveError; end
12 class RemainJobsError < RecursiveError; end
13
14 def self.StateError(state)
15 case state
16 when :run
17 AlreadyRunningError
18 when :done
19 AlreadyExecutedError
20 when :cancel
21 AlreadyCanceledError
22 end
23 end
24 end
+0
-212
vendor/delayer/extend.rb less more
0 # -*- coding: utf-8 -*-
1
2 module Delayer
3 attr_reader :priority
4
5 Bucket = Struct.new(:first, :last, :priority_of, :stashed) do
6 def stash_size
7 if stashed
8 1 + stashed.stash_size
9 else
10 0
11 end
12 end
13 end
14
15 def self.included(klass)
16 klass.class_eval do
17 extend Extend
18 end
19 end
20
21 def initialize(priority = self.class.instance_eval{ @default_priority }, *args)
22 self.class.validate_priority priority
23 @priority = priority
24 @procedure = Procedure.new(self, &Proc.new)
25 end
26
27 # Cancel this job
28 # ==== Exception
29 # Delayer::AlreadyExecutedError :: if already called run()
30 # ==== Return
31 # self
32 def cancel
33 @procedure.cancel
34 self
35 end
36
37 module Extend
38 attr_accessor :expire
39 attr_reader :exception
40
41 def self.extended(klass)
42 klass.class_eval do
43 @busy = false
44 @expire = 0
45 @remain_hook = nil
46 @exception = nil
47 @remain_received = false
48 @lock = Mutex.new
49 @bucket = Bucket.new(nil, nil, {}, nil)
50 end
51 end
52
53 # Run registered jobs.
54 # ==== Args
55 # [current_expire] expire for processing (secs, 0=unexpired)
56 # ==== Return
57 # self
58 def run(current_expire = @expire)
59 if 0 == current_expire
60 run_once while not empty?
61 else
62 @end_time = Time.new.to_f + @expire
63 run_once while not(empty?) and @end_time >= Time.new.to_f
64 @end_time = nil
65 end
66 if @remain_hook
67 @remain_received = !empty?
68 @remain_hook.call if @remain_received
69 end
70 rescue Exception => e
71 @exception = e
72 raise e
73 end
74
75 def expire?
76 if defined?(@end_time) and @end_time
77 @end_time < Time.new.to_f
78 else
79 false
80 end
81 end
82
83 # Run a job and forward pointer.
84 # ==== Return
85 # self
86 def run_once
87 if @bucket.first
88 @busy = true
89 procedure = forward
90 procedure = forward while @bucket.first and procedure.canceled?
91 procedure.run unless procedure.canceled?
92 end
93 ensure
94 @busy = false
95 end
96
97 # Return if some jobs processing now.
98 # ==== Args
99 # [args]
100 # ==== Return
101 # true if Delayer processing job
102 def busy?
103 @busy
104 end
105
106 # Return true if no jobs has.
107 # ==== Return
108 # true if no jobs has.
109 def empty?
110 !@bucket.first
111 end
112
113 # Return remain jobs quantity.
114 # ==== Return
115 # Count of remain jobs
116 def size(node = @bucket.first)
117 if node
118 1 + size(node.next)
119 else
120 0
121 end
122 end
123
124 # register new job.
125 # ==== Args
126 # [procedure] job(Delayer::Procedure)
127 # ==== Return
128 # self
129 def register(procedure)
130 priority = procedure.delayer.priority
131 lock.synchronize do
132 last_pointer = get_prev_point(priority)
133 if last_pointer
134 @bucket.priority_of[priority] = last_pointer.break procedure
135 else
136 procedure.next = @bucket.first
137 @bucket.priority_of[priority] = @bucket.first = procedure
138 end
139 if @bucket.last
140 @bucket.last = @bucket.priority_of[priority]
141 end
142 if @remain_hook and not @remain_received
143 @remain_received = true
144 @remain_hook.call
145 end
146 end
147 self
148 end
149
150 def register_remain_hook
151 @remain_hook = Proc.new
152 end
153
154 def get_prev_point(priority)
155 if @bucket.priority_of[priority]
156 @bucket.priority_of[priority]
157 else
158 next_index = @priorities.index(priority) - 1
159 get_prev_point @priorities[next_index] if 0 <= next_index
160 end
161 end
162
163 def validate_priority(symbol)
164 unless @priorities.include? symbol
165 raise Delayer::InvalidPriorityError, "undefined priority '#{symbol}'"
166 end
167 end
168
169 # DelayerのStashレベルをインクリメントする。
170 # このメソッドが呼ばれたら、その時存在するジョブは退避され、stash_exit!が呼ばれるまで実行されない。
171 def stash_enter!
172 @bucket = Bucket.new(nil, nil, {}, @bucket)
173 self
174 end
175
176 # DelayerのStashレベルをデクリメントする。
177 # このメソッドを呼ぶ前に、現在のレベルに存在するすべてのジョブを実行し、Delayer#empty?がtrueを返すような状態になっている必要がある。
178 # ==== Raises
179 # [Delayer::NoLowerLevelError] stash_enter!が呼ばれていない時
180 # [Delayer::RemainJobsError] ジョブが残っているのにこのメソッドを呼んだ時
181 def stash_exit!
182 raise Delayer::NoLowerLevelError, 'stash_exit! called in level 0.' if !@bucket.stashed
183 raise Delayer::RemainJobsError, 'Current level has remain jobs. It must be empty current level jobs in call this method.' if !self.empty?
184 @bucket = @bucket.stashed
185 end
186
187 # 現在のDelayer Stashレベルを返す。
188 def stash_level
189 @bucket.stash_size
190 end
191
192 private
193
194 def forward
195 lock.synchronize do
196 prev = @bucket.first
197 @bucket.first = @bucket.first.next
198 @bucket.last = nil unless @bucket.first
199 @bucket.priority_of.each do |priority, pointer|
200 @bucket.priority_of[priority] = @bucket.first if prev == pointer
201 end
202 prev.next = nil
203 prev
204 end
205 end
206
207 def lock
208 @lock
209 end
210 end
211 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 = "1.0.0"
2 end
+0
-39
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 "monitor"
6
7 module Delayer
8 class << self
9 attr_accessor :default
10
11 # Generate new Delayer class.
12 # ==== Args
13 # [options]
14 # Hash
15 # expire :: processing expire (secs, 0=unlimited)
16 # priority :: priorities
17 # default :: default priotity
18 # ==== Return
19 # A new class
20 def generate_class(options = {})
21 Class.new do
22 include ::Delayer
23 @expire = options[:expire] || 0
24 if options.has_key?(:priority)
25 @priorities = options[:priority]
26 @default_priority = options[:default]
27 else
28 @priorities = [:normal]
29 @default_priority = :normal
30 end
31 end
32 end
33
34 def method_missing(*args, &proc)
35 (@default ||= generate_class).__send__(*args, &proc)
36 end
37 end
38 end
+0
-33
vendor/diva/datasource.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin rdoc
3 データの保存/復元を実際に担当するデータソース。
4 データソースをモデルにModel::add_data_retrieverにて幾つでも参加させることが出来る。
5 =end
6 module Diva::DataSource
7 USE_ALL = -1 # findbyidの引数。全てのDataSourceから探索する
8 USE_LOCAL_ONLY = -2 # findbyidの引数。ローカルにあるデータのみを使う
9
10 attr_accessor :keys
11
12 # idをもつデータを返す。
13 # もし返せない場合は、nilを返す
14 def findbyid(id, policy)
15 nil
16 end
17
18 # 取得できたらそのDivaのインスタンスをキーにして実行されるDeferredを返す
19 def idof(id)
20 Thread.new{ findbyid(id) } end
21 alias [] idof
22
23 # データの保存
24 # データ一件保存する。保存に成功したか否かを返す。
25 def store_datum(datum)
26 false
27 end
28
29 def inspect
30 self.class.to_s
31 end
32 end
+0
-19
vendor/diva/error.rb less more
0 # -*- coding: utf-8 -*-
1 module Diva
2 class DivaError < StandardError; end
3
4 class InvalidTypeError < DivaError; end
5
6 class InvalidEntityError < DivaError; end
7
8 # 実装してもしなくてもいいメソッドが実装されておらず、結果を得られない
9 class NotImplementedError < DivaError; end
10
11 # IDやURIなどの一意にリソースを特定する情報を使ってデータソースに問い合わせたが、
12 # 対応する情報が見つからず、Modelを作成できない
13 class ModelNotFoundError < DivaError; end
14
15 # URIとして受け付けられない値を渡された
16 class InvalidURIError < InvalidTypeError; end
17
18 end
+0
-49
vendor/diva/field.rb less more
0 # -*- coding: utf-8 -*-
1
2 require 'diva/type'
3
4 =begin rdoc
5 Modelのキーの情報を格納する。
6 キーひとつにつき1つのインスタンスが作られる。
7 =end
8 module Diva
9 class Field
10 attr_reader :name, :type, :required
11
12 # [name] Symbol フィールドの名前
13 # [type] Symbol フィールドのタイプ。:int, :string, :bool, :time のほか、Diva::Modelのサブクラスを指定する
14 # [required] boolean _true_ なら、この項目を必須とする
15 def initialize(name, type, required: false)
16 @name = name.to_sym
17 @type = Diva::Type.optional(Diva::Type(type))
18 @required = !!required
19 end
20
21 def dump_for_json(value)
22 type.dump_for_json(value)
23 end
24
25 def required?
26 required
27 end
28
29 def schema
30 {
31 name: @name.to_s,
32 constraint: @type.schema
33 }
34 end
35
36 def to_sym
37 name
38 end
39
40 def to_s
41 name.to_s
42 end
43
44 def inspect
45 "#<#{self.class}: #{name}(#{type})#{required ? '*' : ''}>"
46 end
47 end
48 end
+0
-39
vendor/diva/field_generator.rb less more
0 # -*- coding: utf-8 -*-
1
2 class Diva::FieldGenerator
3 def initialize(model_klass)
4 @model_klass = model_klass
5 end
6
7 def int(field_name, required: false)
8 @model_klass.add_field(field_name, type: :int, required: required)
9 end
10
11 def string(field_name, required: false)
12 @model_klass.add_field(field_name, type: :string, required: required)
13 end
14
15 def bool(field_name, required: false)
16 @model_klass.add_field(field_name, type: :bool, required: required)
17 end
18
19 def time(field_name, required: false)
20 @model_klass.add_field(field_name, type: :time, required: required)
21 end
22
23 def uri(field_name, required: false)
24 @model_klass.add_field(field_name, type: :uri, required: required)
25 end
26
27 def has(field_name, type, required: false)
28 @model_klass.add_field(field_name, type: type, required: required)
29 end
30 end
31
32
33
34
35
36
37
38
+0
-163
vendor/diva/model.rb less more
0 # -*- coding: utf-8 -*-
1 =begin rdoc
2 いろんなリソースの基底クラス
3 =end
4
5 require 'diva/model_extend'
6 require 'diva/uri'
7 require 'diva/spec'
8
9 require 'securerandom'
10
11 class Diva::Model
12 include Comparable
13 extend Diva::ModelExtend
14
15 def initialize(args)
16 @value = args.dup
17 validate
18 self.class.store_datum(self)
19 end
20
21 # データをマージする。
22 # selfにあってotherにもあるカラムはotherの内容で上書きされる。
23 # 上書き後、データはDataSourceに保存される
24 def merge(other)
25 @value.update(other.to_hash)
26 validate
27 self.class.store_datum(self)
28 end
29
30 # このModelのパーマリンクを返す。
31 # パーマリンクはWebのURLで、Web上のリソースでない場合はnilを返す。
32 # ==== Return
33 # 次のいずれか
34 # [URI::HTTP|Diva::URI] パーマリンク
35 # [nil] パーマリンクが存在しない
36 def perma_link
37 nil
38 end
39
40 # このModelのURIを返す。
41 # ==== Return
42 # [URI::Generic|Diva::URI] パーマリンク
43 def uri
44 perma_link || Diva::URI.new("#{self.class.scheme}://#{self.class.host}#{path}")
45 end
46
47 # このModelが、登録されているアカウントのうちいずれかが作成したものであれば true を返す
48 # ==== Args
49 # [service] Service | Enumerable 「自分」のService
50 # ==== Return
51 # [true] 自分のによって作られたオブジェクトである
52 # [false] 自分のによって作られたオブジェクトではない
53 def me?(service=nil)
54 false
55 end
56
57 def hash
58 @_hash ||= self.uri.to_s.hash ^ self.class.hash
59 end
60
61 def <=>(other)
62 if other.is_a?(Diva::Model)
63 created - other.created
64 elsif other.respond_to?(:[]) and other[:created]
65 created - other[:created]
66 else
67 id - other
68 end
69 end
70
71 def ==(other)
72 if other.is_a? Diva::Model
73 self.class == other.class && uri == other.uri
74 end
75 end
76
77 def eql?(other)
78 self == other
79 end
80
81 def to_hash
82 Hash[self.class.fields.map{|f| [f.name, fetch(f.name)] }]
83 end
84
85 def to_json(*rest, **kwrest)
86 Hash[
87 self.class.fields.map{|f| [f.name, f.dump_for_json(fetch(f.name))] }
88 ].to_json(*rest, **kwrest)
89 end
90
91 # カラムの生の内容を返す
92 def fetch(key)
93 @value[key.to_sym]
94 end
95 alias [] fetch
96
97 # カラムに別の値を格納する。
98 # 格納後、データはDataSourceに保存される
99 def []=(key, value)
100 @value[key.to_sym] = value
101 self.class.store_datum(self)
102 value
103 end
104
105 # カラムと型が違うものがある場合、例外を発生させる。
106 def validate
107 raise RuntimeError, "argument is #{@value}, not Hash" if not @value.is_a?(Hash)
108 self.class.fields.each do |field|
109 begin
110 @value[field.name] = field.type.cast(@value[field.name])
111 rescue Diva::InvalidTypeError => err
112 raise Diva::InvalidTypeError, "#{err} in field `#{field}'"
113 end
114 end
115 end
116
117 # キーとして定義されていない値を全て除外した配列を生成して返す。
118 # また、Modelを子に含んでいる場合、それを外部キーに変換する。
119 def filtering
120 datum = self.to_hash
121 result = Hash.new
122 self.class.fields.each do |field|
123 begin
124 result[field.name] = field.type.cast(datum[field.name])
125 rescue Diva::InvalidTypeError => err
126 raise Diva::InvalidTypeError, "#{err} in field `#{field}'"
127 end
128 end
129 result
130 end
131
132 # このインスタンスのタイトル。
133 def title
134 fields = self.class.fields.lazy.map(&:name)
135 case
136 when fields.include?(:name)
137 name.gsub("\n", '')
138 when fields.include?(:description)
139 description.gsub("\n", '')
140 else
141 to_s.gsub("\n", '')
142 end
143 end
144
145 def dig(key, *args)
146 return nil unless key.respond_to?(:to_sym)
147 value = fetch(key)
148 if value.nil? || args.empty?
149 value
150 else
151 value.dig(*args)
152 end
153 end
154
155 private
156 # URIがデフォルトで使うpath要素
157 def path
158 @path ||= "/#{SecureRandom.uuid}"
159 end
160
161 end
162
+0
-105
vendor/diva/model_extend.rb less more
0 # -*- coding: utf-8 -*-
1
2 require 'diva/field'
3 require 'diva/type'
4
5 =begin rdoc
6 Diva::Model のクラスメソッド
7 =end
8 module Diva::ModelExtend
9 extend Gem::Deprecate
10
11 attr_reader :slug, :spec
12
13 # Modelのインスタンスのuriスキーム。オーバライドして適切な値にする
14 # ==== Return
15 # [String] URIスキーム
16 def scheme
17 @_scheme ||= self.to_s.split('::',2).first.gsub(/\W/,'').downcase.freeze
18 end
19
20 # Modelのインスタンスのホスト名。オーバライドして適切な値にする
21 # ==== Return
22 # [String] ホスト名
23 def host
24 @_host ||= self.to_s.split('::',2).last.split('::').reverse.join('.').gsub(/[^\w\.]/,'').downcase.freeze
25 end
26
27 # Modelにフィールドを追加する。
28 # ==== Args
29 # [field_name] Symbol フィールドの名前
30 # [type] Symbol フィールドのタイプ。:int, :string, :bool, :time のほか、Diva::Modelのサブクラスを指定する
31 # [required] boolean _true_ なら、この項目を必須とする
32 def add_field(field, type: nil, required: false)
33 if field.is_a?(Symbol)
34 field = Diva::Field.new(field, type, required: required)
35 end
36 (@fields ||= []) << field
37 define_method(field.name) do
38 @value[field.name]
39 end
40
41 define_method("#{field.name}?") do
42 !!@value[field.name]
43 end
44
45 define_method("#{field.name}=") do |value|
46 @value[field.name] = field.type.cast(value)
47 self.class.store_datum(self)
48 value
49 end
50 self
51 end
52
53 def fields
54 @fields ||= []
55 end
56 alias :keys :fields
57 deprecate :keys, "fields", 2018, 02
58
59 def schema
60 {
61 fields: fields.map(&:schema),
62 uri: uri
63 }
64 end
65
66 def uri
67 @uri ||= Diva::URI("diva://object.type/#{slug || SecureRandom.uuid}")
68 end
69
70 #
71 # プライベートクラスメソッド
72 #
73
74 def field
75 Diva::FieldGenerator.new(self)
76 end
77
78 # URIに対応するリソースの内容を持ったModelを作成する。
79 # URIに対応する情報はネットワーク上などから取得される場合もある。そういった場合はこのメソッドは
80 # Delayer::Deferred::Deferredable を返す可能性がある。
81 # とくにオーバライドしない場合、このメソッドは常に例外 Diva::NotImplementedError を投げる。
82 # ==== Args
83 # [uri] _handle_ メソッドで指定したいずれかの条件に一致するURI
84 # ==== Return
85 # [Delayer::Deferred::Deferredable]
86 # ネットワークアクセスを行って取得するなど取得に時間がかかる場合
87 # [self]
88 # すぐにModelを生成できる場合、そのModel
89 # ==== Raise
90 # [Diva::NotImplementedError]
91 # このModelでは、find_by_uriが実装されていない
92 # [Diva::ModelNotFoundError]
93 # _uri_ に対応するリソースが見つからなかった
94 def find_by_uri(uri)
95 raise Diva::NotImplementedError, "#{self}.find_by_uri does not implement."
96 end
97
98 # Modelが生成・更新された時に呼ばれるコールバックメソッドです
99 def store_datum(retriever); end
100
101 def container_class
102 Array
103 end
104 end
+0
-9
vendor/diva/spec.rb less more
0 # -*- coding: utf-8 -*-
1
2 Diva::ModelSpec = Struct.new(
3 :slug,
4 :name,
5 :reply,
6 :myself,
7 :timeline,
8 )
+0
-319
vendor/diva/type.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin rdoc
3 Modelの各キーに格納できる値の制約。
4 この制約に満たない場合は、アトミックな制約であれば値の変換が行われ、そうでない場合は
5 Diva::InvalidTypeError 例外を投げる。
6
7 これは新しくインスタンスなどを作らず、INT、FLOATなどのプリセットを利用する。
8
9 == 設定できる制約
10 Modelフィールドの制約には以下のようなものがある。
11
12 === アトミックな制約
13 以下のような値は、DivaのModelフィールドにおいてはアトミックな制約と呼び、そのまま格納できる。
14 [INT] 数値(Integer)
15 [FLOAT] 少数(Float)
16 [BOOL] 真理値(true|false)
17 [STRING] 文字列(String)
18 [TIME] 時刻(Time)
19 [URI] URI(Diva::URI|URI::Generic|Addressable::URI)
20
21 === Model
22 Diva::Modelのサブクラスであれば、それを制約とすることができる。
23
24 === 配列
25 アトミックな制約またはModel制約を満たした値の配列を格納することができる。
26 配列の全ての要素が設定された制約を満たしていれば、配列制約が満たされたことになる。
27
28 =end
29 require "time"
30 require 'diva/uri'
31
32 module Diva::Type
33 extend self
34
35 def model_of(model)
36 ModelType.new(model)
37 end
38
39 def array_of(type)
40 ArrayType.new(type)
41 end
42
43 def optional(type)
44 UnionType.new(NULL, type)
45 end
46
47 def union(*types)
48 UnionType.new(*types)
49 end
50
51 # 全てのType共通のスーパークラス
52 class MetaType
53 attr_reader :name
54
55 def initialize(name, *, &cast)
56 @name = name.to_sym
57 if cast
58 define_singleton_method :cast, &cast
59 end
60 end
61
62 def cast(value)
63 value
64 end
65
66 def to_s
67 name.to_s
68 end
69
70 def dump_for_json(value)
71 value
72 end
73
74 def inspect
75 "Diva::Type(#{to_s})"
76 end
77 end
78
79 class AtomicType < MetaType
80 def initialize(name, recommended_class, *rest, &cast)
81 super(name, *rest, &cast)
82 @recommended_classes = Array(recommended_class)
83 end
84
85 def recommendation_point(value)
86 _, point = @recommended_classes.map.with_index{|k, i| [k, i] }.find{|k, _| value.is_a?(k) }
87 point
88 end
89
90 def schema
91 @schema ||= {type: uri}.freeze
92 end
93
94 def uri
95 @uri ||= Diva::URI("diva://atomic.type/#{name}")
96 end
97 end
98
99 INT = AtomicType.new(:int, [Integer]) do |v|
100 case v
101 when Integer
102 v
103 when Numeric, String, Time
104 v.to_i
105 when TrueClass
106 1
107 when FalseClass
108 0
109 else
110 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
111 end
112 end
113 FLOAT = AtomicType.new(:float, [Float]) do |v|
114 case v
115 when Float
116 v
117 when Numeric, String, Time
118 v.to_f
119 else
120 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
121 end
122 end
123 BOOL = AtomicType.new(:bool, [TrueClass, FalseClass]) do |v|
124 case v
125 when TrueClass, FalseClass
126 v
127 when String
128 !v.empty?
129 when Integer
130 v != 0
131 else
132 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
133 end
134 end
135 STRING = AtomicType.new(:string, String) do |v|
136 case v
137 when Diva::Model, Enumerable
138 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
139 else
140 v.to_s
141 end
142 end
143 class TimeType < AtomicType
144 def dump_for_json(value)
145 cast(value).iso8601
146 end
147 end
148 TIME = TimeType.new(:time, [Time, String]) do |v|
149 case v
150 when Time
151 v
152 when Integer, Float
153 Time.at(v)
154 when String
155 Time.iso8601(v)
156 else
157 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
158 end
159 end
160 URI = AtomicType.new(:uri, [Diva::URI, Addressable::URI, ::URI::Generic]) do |v|
161 case v
162 when Diva::URI, Addressable::URI, ::URI::Generic
163 v
164 when String
165 Diva::URI.new(v)
166 else
167 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
168 end
169 end
170 NULL = AtomicType.new(:null, NilClass) do |v|
171 if v.nil?
172 v
173 else
174 raise Diva::InvalidTypeError, "The value is not a `#{name}'."
175 end
176 end
177
178 class ModelType < MetaType
179 attr_reader :model
180 def initialize(model, *rest, &cast)
181 super(:model, *rest)
182 @model = model
183 end
184
185 def recommendation_point(v)
186 v.is_a?(model) && 0
187 end
188
189 def cast(value)
190 case value
191 when model
192 value
193 when Hash
194 model.new(value)
195 else
196 raise Diva::InvalidTypeError, "The value #{value.inspect} is not a `#{model}'."
197 end
198 end
199
200 def schema
201 @schema ||= {type: uri}.freeze
202 end
203
204 def to_s
205 "#{model} #{name}"
206 end
207
208 def uri
209 model.uri
210 end
211 end
212
213 class ArrayType < MetaType
214 def initialize(type)
215 type = Diva::Type(type)
216 super("#{type.name}_array")
217 @type = type
218 end
219
220 def recommendation_point(values)
221 values.is_a?(Enumerable) && values.all?{|v| @type.recommendation_point(v) } && 0
222 end
223
224 def cast(value)
225 raise Diva::InvalidTypeError, "The value is not a `#{name}'." unless value.is_a?(Enumerable)
226 value.to_a.map(&@type.method(:cast))
227 end
228
229 def dump_for_json(value)
230 value.to_a.map(&@type.method(:dump_for_json))
231 end
232
233 def to_s
234 "Array of #{@type.to_s}"
235 end
236
237 def schema
238 @schema ||= { array: @type.schema }.freeze
239 end
240 end
241
242 class UnionType < MetaType
243 def initialize(*types)
244 @types = types.flatten.map(&Diva.method(:Type)).freeze
245 super("union_#{@types.map(&:name).join('_')}")
246 end
247
248 def recommendation_point(v)
249 @types.map{|t| t.recommendation_point(v) }.compact.min
250 end
251
252 def cast(value)
253 recommended_type_of(value).cast(value)
254 end
255
256 def dump_for_json(value)
257 recommended_type_of(value).dump_for_json(value)
258 end
259
260 def to_s
261 @types.map(&:name).join('|').freeze
262 end
263
264 def schema
265 @schema ||= { union: @types.map(&:schema) }.freeze
266 end
267
268 def recommended_type_of(value)
269 @types.map{|t|
270 [t, t.recommendation_point(value)]
271 }.select{|_,p| p }.sort_by{|_,p| p }.each{|t,_|
272 return t
273 }
274 @types.each do |type|
275 begin
276 type.cast(value)
277 return type
278 rescue Diva::InvalidTypeError
279 # try next
280 end
281 end
282 raise Diva::InvalidTypeError, "The value is not #{self}"
283 end
284 end
285 end
286
287 module Diva
288 def self.Type(type)
289 case type
290 when Diva::Type::MetaType
291 type
292 when :int
293 Diva::Type::INT
294 when :float
295 Diva::Type::FLOAT
296 when :bool
297 Diva::Type::BOOL
298 when :string
299 Diva::Type::STRING
300 when :time
301 Diva::Type::TIME
302 when :uri
303 Diva::Type::URI
304 when :null
305 Diva::Type::NULL
306 when ->x{x.class == Class && x.ancestors.include?(Diva::Model) }
307 Diva::Type.model_of(type)
308 when Array
309 if type.size >= 2
310 Diva::Type.union(*type)
311 else
312 Diva::Type.array_of(type.first)
313 end
314 else
315 fail "Invalid type #{type.inspect} (#{type.class})."
316 end
317 end
318 end
+0
-132
vendor/diva/uri.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin rdoc
3 =Model用のURIクラス
4
5 mikutterでは、 URI や Addressable::URI の代わりに、このクラスを使用します。
6 URI や Addressable::URI に比べて、次のような特徴があります。
7
8 * コンストラクタに文字列を渡している場合、 _to_s_ がその文字列を返す。
9 * 正規化しないかわりに高速に動作します。
10 * Diva::URI のインスタンスは URI と同じように使える
11 * unicode文字などが入っていて URI では表現できない場合、 Addressable::URI を使う
12 * Addressable::URIでないと表現できないURIであればそちらを使うという判断を自動で行う
13
14 == 使い方
15 Diva::URI() メソッドの引数にString, URI, Addressable::URI, Hash, Diva::URIのいずれかを与えます。
16
17 [String] uriの文字列(ex: "http://mikutter.hachune.net/")
18 [URI] URI のインスタンス
19 [Addressable::URI] Addressable::URI のインスタンス
20 [Hash] これを引数にURI::Generic.build に渡すのと同じ形式の Hash
21 [Diva::URI] 即座にこれ自身を返す
22
23 == 例
24
25 Diva::URI("http://mikutter.hachune.net/")
26 Diva::URI(URI::Generic.build(scheme: 'http', host: 'mikutter.hachune.net'))
27 =end
28
29 require 'uri'
30 require 'addressable/uri'
31
32 class Diva::URI
33 def initialize(uri)
34 @uri = @uri_string = @uri_hash = nil
35 case uri.freeze
36 when URI, Addressable::URI
37 @uri = uri
38 when String
39 @uri_string = uri
40 when Hash
41 @uri_hash = uri
42 end
43 end
44
45 def ==(other)
46 case other
47 when URI, Addressable::URI, String
48 other.to_s == self.to_s
49 when Diva::URI
50 if has_string? or other.has_string?
51 to_s == other.to_s
52 else
53 other.to_uri == to_uri
54 end
55 end
56 end
57
58 def hash
59 to_s.hash ^ self.class.hash
60 end
61
62 def has_string?
63 !!@uri_string
64 end
65
66 def has_uri?
67 !!@uri
68 end
69
70 def to_s
71 @uri_string ||= to_uri.to_s.freeze
72 end
73
74 def to_uri
75 @uri ||= generate_uri.freeze
76 end
77
78 def scheme
79 if has_string? and !has_uri?
80 match = @uri_string.match(%r<\A(\w+):>)
81 if match
82 match[1]
83 else
84 to_uri.scheme
85 end
86 else
87 to_uri.scheme
88 end
89 end
90
91 def freeze
92 unless frozen?
93 to_uri
94 to_s
95 end
96 super
97 end
98
99 def respond_to_missing?(method, include_private)
100 to_uri.respond_to?(method, include_private)
101 end
102
103 def method_missing(method, *rest, &block)
104 to_uri.__send__(method, *rest, &block)
105 end
106
107 private
108
109 def generate_uri
110 if @uri
111 @uri
112 elsif @uri_string
113 @uri = generate_uri_by_string
114 elsif @uri_hash
115 @uri = generate_uri_by_hash
116 end
117 @uri
118 end
119
120 def generate_uri_by_string
121 URI.parse(@uri_string)
122 rescue URI::Error
123 Addressable::URI.parse(@uri_string)
124 end
125
126 def generate_uri_by_hash
127 URI::Generic.build(@uri_hash)
128 rescue URI::Error
129 Addressable::URI.new(@uri_hash)
130 end
131 end
+0
-3
vendor/diva/version.rb less more
0 module Diva
1 VERSION = "1.0.0"
2 end
+0
-18
vendor/diva.rb less more
0 # coding: utf-8
1 require 'diva/version'
2 require 'diva/datasource'
3 require 'diva/error'
4 require 'diva/field_generator'
5 require 'diva/field'
6 require 'diva/model'
7 require 'diva/spec'
8 require 'diva/type'
9 require 'diva/uri'
10 require 'diva/version'
11
12
13 module Diva
14 def self.URI(uri)
15 Diva::URI.new(uri)
16 end
17 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
-115
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.map {|path|
50 path = path.to_path if path.respond_to?(:to_path)
51 path.gsub(/\/lib\z/, "")
52 }
53 load_path.each {|path|
54 default_path_rules += [
55 "#{path}/data/locale/%{lang}/LC_MESSAGES/%{name}.mo",
56 "#{path}/data/locale/%{lang}/%{name}.mo",
57 "#{path}/locale/%{lang}/LC_MESSAGES/%{name}.mo",
58 "#{path}/locale/%{lang}/%{name}.mo",
59 ]
60 }
61 # paths existed only.
62 default_path_rules = default_path_rules.select{|path|
63 Dir.glob(path % {:lang => "*", :name => "*"}).size > 0}.uniq
64 default_path_rules
65 end
66 end
67
68 attr_reader :locale_paths, :supported_locales
69
70 # Creates a new GetText::TextDomain.
71 # * name: the textdomain name.
72 # * topdir: the locale path ("%{topdir}/%{lang}/LC_MESSAGES/%{name}.mo") or nil.
73 def initialize(name, topdir = nil)
74 @name = name
75
76 if topdir
77 path_rules = ["#{topdir}/%{lang}/LC_MESSAGES/%{name}.mo", "#{topdir}/%{lang}/%{name}.mo"]
78 else
79 path_rules = self.class.default_path_rules
80 end
81
82 @locale_paths = {}
83 path_rules.each do |rule|
84 this_path_rules = rule % {:lang => "([^\/]+)", :name => name}
85 Dir.glob(rule % {:lang => "*", :name => name}).each do |path|
86 if /#{this_path_rules}/ =~ path
87 @locale_paths[$1] = path.untaint unless @locale_paths[$1]
88 end
89 end
90 end
91 @supported_locales = @locale_paths.keys.sort
92 end
93
94 # Gets the current path.
95 # * lang: a Locale::Tag.
96 def current_path(lang)
97 lang_candidates = lang.candidates
98
99 lang_candidates.each do |tag|
100 path = @locale_paths[tag.to_s]
101 warn "GetText::TextDomain#load_mo: mo-file is #{path}" if $DEBUG
102 return path if path
103 end
104
105 if $DEBUG
106 warn "MO file is not found in"
107 @locale_paths.each do |path|
108 warn " #{path[1]}"
109 end
110 end
111 nil
112 end
113 end
114 end
+0
-362
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-2017 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".b
46 MAGIC_LITTLE_ENDIAN = "\xde\x12\x04\x95".b
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 @charset = nil
58 @output_charset = output_charset
59 @plural_proc = nil
60 super()
61 end
62
63 def store(msgid, msgstr, options)
64 string = generate_original_string(msgid, options)
65 self[string] = msgstr
66 end
67
68 def update!
69 if FileTest.exist?(@filename)
70 st = File.stat(@filename)
71 load(@filename) unless (@last_modified == [st.ctime, st.mtime])
72 else
73 warn "#{@filename} was lost." if $DEBUG
74 clear
75 end
76 self
77 end
78
79 def load(arg)
80 if arg.kind_of? String
81 begin
82 st = File.stat(arg)
83 @last_modified = [st.ctime, st.mtime]
84 rescue Exception
85 end
86 load_from_file(arg)
87 else
88 load_from_stream(arg)
89 end
90 @filename = arg
91 self
92 end
93
94 def load_from_stream(io)
95 magic = io.read(4)
96 case magic
97 when MAGIC_BIG_ENDIAN
98 @little_endian = false
99 when MAGIC_LITTLE_ENDIAN
100 @little_endian = true
101 else
102 raise InvalidFormat.new(sprintf("Unknown signature %s", magic.dump))
103 end
104
105 endian_type6 = @little_endian ? 'V6' : 'N6'
106 endian_type_astr = @little_endian ? 'V*' : 'N*'
107
108 header = HeaderRev1.new(magic, *(io.read(4 * 6).unpack(endian_type6)))
109
110 if header.revision == 1
111 # FIXME: It doesn't support sysdep correctly.
112 header.n_sysdep_segments = io.read(4).unpack(endian_type6)
113 header.sysdep_segments_offset = io.read(4).unpack(endian_type6)
114 header.n_sysdep_strings = io.read(4).unpack(endian_type6)
115 header.orig_sysdep_tab_offset = io.read(4).unpack(endian_type6)
116 header.trans_sysdep_tab_offset = io.read(4).unpack(endian_type6)
117 elsif header.revision > 1
118 raise InvalidFormat.new(sprintf("file format revision %d isn't supported", header.revision))
119 end
120 io.pos = header.orig_table_offset
121 orig_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
122
123 io.pos = header.translated_table_offset
124 trans_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
125
126 msgids = Array.new(header.nstrings)
127 for i in 0...header.nstrings
128 io.pos = orig_table_data[i * 2 + 1]
129 msgids[i] = io.read(orig_table_data[i * 2 + 0])
130 end
131
132 clear
133 for i in 0...header.nstrings
134 io.pos = trans_table_data[i * 2 + 1]
135 msgstr = io.read(trans_table_data[i * 2 + 0])
136
137 msgid = msgids[i]
138 if msgid.nil? || msgid.empty?
139 if msgstr
140 @charset = nil
141 @nplurals = nil
142 @plural = nil
143 msgstr.each_line{|line|
144 if /^Content-Type:/i =~ line and /charset=((?:\w|-)+)/i =~ line
145 @charset = $1
146 elsif /^Plural-Forms:\s*nplurals\s*\=\s*(\d*);\s*plural\s*\=\s*([^;]*)\n?/ =~ line
147 @nplurals = $1
148 @plural = $2
149 end
150 break if @charset and @nplurals
151 }
152 @nplurals = "1" unless @nplurals
153 @plural = "0" unless @plural
154 end
155 else
156 unless msgstr.nil?
157 msgstr = convert_encoding(msgstr, msgid)
158 end
159 end
160 msgid.force_encoding(@charset) if @charset
161 self[msgid] = msgstr.freeze
162 end
163 self
164 end
165
166 def prime?(number)
167 ('1' * number) !~ /^1?$|^(11+?)\1+$/
168 end
169
170 begin
171 require 'prime'
172 def next_prime(seed)
173 Prime.instance.find{|x| x > seed }
174 end
175 rescue LoadError
176 def next_prime(seed)
177 require 'mathn'
178 prime = Prime.new
179 while current = prime.succ
180 return current if current > seed
181 end
182 end
183 end
184
185 HASHWORDBITS = 32
186 # From gettext-0.12.1/gettext-runtime/intl/hash-string.h
187 # Defines the so called `hashpjw' function by P.J. Weinberger
188 # [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
189 # 1986, 1987 Bell Telephone Laboratories, Inc.]
190 def hash_string(str)
191 hval = 0
192 str.each_byte do |b|
193 break if b == '\0'
194 hval <<= 4
195 hval += b.to_i
196 g = hval & (0xf << (HASHWORDBITS - 4))
197 if (g != 0)
198 hval ^= g >> (HASHWORDBITS - 8)
199 hval ^= g
200 end
201 end
202 hval
203 end
204
205 #Save data as little endian format.
206 def save_to_stream(io)
207 # remove untranslated message
208 translated_messages = reject do |msgid, msgstr|
209 msgstr.nil?
210 end
211
212 size = translated_messages.size
213 header_size = 4 * 7
214 table_size = 4 * 2 * size
215
216 hash_table_size = next_prime((size * 4) / 3)
217 hash_table_size = 3 if hash_table_size <= 2
218 header = Header.new(
219 MAGIC_LITTLE_ENDIAN, # magic
220 0, # revision
221 size, # nstrings
222 header_size, # orig_table_offset
223 header_size + table_size, # translated_table_offset
224 hash_table_size, # hash_table_size
225 header_size + table_size * 2 # hash_table_offset
226 )
227 io.write(header.to_a.pack('a4V*'))
228
229 ary = translated_messages.to_a
230 ary.sort!{|a, b| a[0] <=> b[0]} # sort by original string
231
232 pos = header.hash_table_size * 4 + header.hash_table_offset
233
234 orig_table_data = Array.new()
235 ary.each{|item, _|
236 orig_table_data.push(item.bytesize)
237 orig_table_data.push(pos)
238 pos += item.bytesize + 1 # +1 is <NUL>
239 }
240 io.write(orig_table_data.pack('V*'))
241
242 trans_table_data = Array.new()
243 ary.each{|_, item|
244 trans_table_data.push(item.bytesize)
245 trans_table_data.push(pos)
246 pos += item.bytesize + 1 # +1 is <NUL>
247 }
248 io.write(trans_table_data.pack('V*'))
249
250 hash_tab = Array.new(hash_table_size)
251 j = 0
252 ary[0...size].each {|key, _|
253 hash_val = hash_string(key)
254 idx = hash_val % hash_table_size
255 if hash_tab[idx] != nil
256 incr = 1 + (hash_val % (hash_table_size - 2))
257 begin
258 if (idx >= hash_table_size - incr)
259 idx -= hash_table_size - incr
260 else
261 idx += incr
262 end
263 end until (hash_tab[idx] == nil)
264 end
265 hash_tab[idx] = j + 1
266 j += 1
267 }
268 hash_tab.collect!{|i| i ? i : 0}
269
270 io.write(hash_tab.pack('V*'))
271
272 ary.each{|item, _| io.write(item); io.write("\0") }
273 ary.each{|_, item| io.write(item); io.write("\0") }
274
275 self
276 end
277
278 def load_from_file(filename)
279 @filename = filename
280 begin
281 File.open(filename, 'rb'){|f| load_from_stream(f)}
282 rescue => e
283 e.set_backtrace("File: #{@filename}")
284 raise e
285 end
286 end
287
288 def save_to_file(filename)
289 File.open(filename, 'wb'){|f| save_to_stream(f)}
290 end
291
292 def set_comment(msgid_or_sym, comment)
293 #Do nothing
294 end
295
296 def plural_as_proc
297 unless @plural_proc
298 @plural_proc = Proc.new{|n| eval(@plural)}
299 begin
300 @plural_proc.call(1)
301 rescue
302 @plural_proc = Proc.new{|n| 0}
303 end
304 end
305 @plural_proc
306 end
307
308 attr_accessor :little_endian, :path, :last_modified
309 attr_reader :charset, :nplurals, :plural
310
311 private
312 def convert_encoding(string, original_string)
313 return string if @output_charset.nil? or @charset.nil?
314
315 if Encoding.find(@output_charset) == Encoding.find(@charset)
316 string.force_encoding(@output_charset)
317 return string
318 end
319
320 string.encode(@output_charset,
321 @charset,
322 :invalid => :replace,
323 :undef => :replace)
324 end
325
326 def generate_original_string(msgid, options)
327 string = String.new
328
329 msgctxt = options.delete(:msgctxt)
330 msgid_plural = options.delete(:msgid_plural)
331
332 string << msgctxt << "\004" unless msgctxt.nil?
333 string << msgid
334 string << "\000" << msgid_plural unless msgid_plural.nil?
335 string
336 end
337 end
338 end
339
340 # Test
341
342 if $0 == __FILE__
343 if (ARGV.include? "-h") or (ARGV.include? "--help")
344 STDERR.puts("mo.rb [filename.mo ...]")
345 exit
346 end
347
348 ARGV.each{ |item|
349 mo = GetText::MO.open(item)
350 puts "------------------------------------------------------------------"
351 puts "charset = \"#{mo.charset}\""
352 puts "nplurals = \"#{mo.nplurals}\""
353 puts "plural = \"#{mo.plural}\""
354 puts "------------------------------------------------------------------"
355 mo.each do |key, value|
356 puts "original message = #{key.inspect}"
357 puts "translated message = #{value.inspect}"
358 puts "--------------------------------------------------------------------"
359 end
360 }
361 end
+0
-275
vendor/gettext/po.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2017 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 = String.new
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-2017 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 ||= String.new
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 = String.new
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 = String.new
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 = String.new
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 = String.new
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 = String.new
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 = String.new
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-2017 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.14
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 = 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, 11, 10, 9, 6, 17, 16, 15, 22, 15,
269 13, 13, 15, 13, 13, 15, 22, 24, 13, 15 ]
270
271 racc_action_check = [
272 1, 2, 1, 1, 1, 14, 14, 14, 19, 19,
273 6, 9, 12, 16, 17, 18, 20, 22, 24, 25 ]
274
275 racc_action_pointer = [
276 nil, 0, 1, nil, nil, nil, 3, nil, nil, 4,
277 nil, nil, 5, nil, 0, nil, 6, 7, 8, 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, 1, 3, 4, 5, 7, 8,
287 18, 19, 20, nil, nil, nil, nil, nil, 25 ]
288
289 racc_goto_check = [
290 5, 9, 9, 5, 1, 2, 3, 4, 6, 7,
291 5, 5, 8, nil, nil, nil, nil, nil, 5 ]
292
293 racc_goto_pointer = [
294 nil, 4, 4, 5, 6, -6, 7, 8, -7, -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
-182
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 plural_msgid_prefix = "#{msgid}\000"
98 mo.each do |key, val|
99 next unless Encoding.compatible?(key, plural_msgid_prefix)
100 next unless key.start_with?(plural_msgid_prefix)
101 ret = val.split("\000")[0]
102 break
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_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_key = lang.to_s
150
151 mo = @mofiles[lang_key]
152 if mo
153 if mo == :empty
154 return :empty
155 elsif ! self.class.cached?
156 mo.update!
157 end
158 return mo
159 end
160
161 path = @locale_path.current_path(lang)
162
163 if path
164 charset = @output_charset || lang.to_posix.charset || Locale.charset || "UTF-8"
165 charset = normalize_charset(charset)
166 @mofiles[lang_key] = MO.open(path, charset)
167 else
168 @mofiles[lang_key] = :empty
169 end
170 end
171
172 def normalize_charset(charset)
173 case charset
174 when /\Autf8\z/i
175 "UTF-8"
176 else
177 charset
178 end
179 end
180 end
181 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
-356
vendor/gettext/tools/msgcat.rb less more
0 # Copyright (C) 2014-2017 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 if entry.msgid == :last
76 next unless @config.output_obsolete_entries?
77 end
78 id = [entry.msgctxt, entry.msgid]
79 if @output_po.has_key?(*id)
80 merged_entry = merge_entry(@output_po[*id], entry)
81 else
82 merged_entry = entry
83 end
84 next unless merged_entry
85 if merged_entry.header?
86 update_po_revision_date!(merged_entry)
87 remove_header_fields!(merged_entry)
88 end
89 @output_po[*id] = merged_entry
90 end
91 end
92
93 private
94 def merge_entry(base_entry, new_entry)
95 if base_entry.header?
96 return merge_header(base_entry, new_entry)
97 end
98
99 if base_entry.fuzzy?
100 return merge_fuzzy_entry(base_entry, new_entry)
101 end
102
103 if base_entry.translated?
104 base_entry
105 else
106 new_entry
107 end
108 end
109
110 def merge_header(base_entry, new_entry)
111 base_entry
112 end
113
114 def merge_fuzzy_entry(base_entry, new_entry)
115 return new_entry unless new_entry.fuzzy?
116 return nil unless @config.include_fuzzy?
117 base_entry
118 end
119
120 def update_po_revision_date!(header_entry)
121 return unless @config.update_po_revision_date?
122
123 now = Time.now.strftime("%Y-%m-%d %H:%M%z")
124 po_revision_date_value = "PO-Revision-Date: #{now}\n"
125 have_po_revision_date = false
126 new_msgstr = String.new
127 header_entry.msgstr.each_line do |line|
128 case line
129 when /\APO-Revision-Date:/
130 new_msgstr << po_revision_date_value
131 have_po_revision_date = true
132 else
133 new_msgstr << line
134 end
135 end
136 unless have_po_revision_date
137 new_msgstr << po_revision_date_value
138 end
139 header_entry.msgstr = new_msgstr
140 end
141
142 def remove_header_fields!(header_entry)
143 remove_header_fields = @config.remove_header_fields
144 return if remove_header_fields.empty?
145 msgstr = header_entry.msgstr
146 return if msgstr.nil?
147
148 new_msgstr = String.new
149 msgstr.each_line do |line|
150 case line
151 when /\A([\w\-]+):/
152 name = $1
153 next if remove_header_fields.include?(name)
154 end
155 new_msgstr << line
156 end
157 header_entry.msgstr = new_msgstr
158 end
159 end
160
161 # @private
162 class Config
163 include GetText
164
165 bindtextdomain("gettext")
166
167 # @return [Array<String>] The input PO file names.
168 attr_accessor :pos
169
170 # @return [String] The output file name.
171 attr_accessor :output
172
173 # @return [:reference, :msgid] The sort key.
174 attr_accessor :order
175
176 # @return [Hash] The PO format options.
177 # @see PO#to_s
178 # @see POEntry#to_s
179 attr_accessor :po_format_options
180
181 # (see include_fuzzy?)
182 attr_writer :include_fuzzy
183
184 # (see report_warning?)
185 attr_writer :report_warning
186
187 # (see output_obsolete_entries?)
188 attr_writer :output_obsolete_entries
189
190 # @see #update_po_revision_date?
191 attr_writer :update_po_revision_date
192
193 # @return [Array<String>] The field names to be removed from
194 # header entry.
195 attr_reader :remove_header_fields
196
197 def initialize
198 @pos = []
199 @output = nil
200 @order = nil
201 @po_format_options = {
202 :max_line_width => POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH,
203 }
204 @include_fuzzy = true
205 @report_warning = true
206 @output_obsolete_entries = true
207 @remove_header_fields = []
208 @update_po_revision_date = false
209 end
210
211 # @return [Boolean] Whether includes fuzzy entries or not.
212 def include_fuzzy?
213 @include_fuzzy
214 end
215
216 # @return [Boolean] Whether reports warning messages or not.
217 def report_warning?
218 @report_warning
219 end
220
221 # @return [Boolean] Whether outputs obsolete entries or not.
222 def output_obsolete_entries?
223 @output_obsolete_entries
224 end
225
226 # @return [Boolean] Whether updates PO-Revision-Date header
227 # field or not.
228 def update_po_revision_date?
229 @update_po_revision_date
230 end
231
232 def parse(command_line)
233 parser = create_option_parser
234 @pos = parser.parse(command_line)
235 end
236
237 private
238 def create_option_parser
239 parser = OptionParser.new
240 parser.version = GetText::VERSION
241 parser.banner = _("Usage: %s [OPTIONS] PO_FILE1 PO_FILE2 ...") % $0
242 parser.separator("")
243 parser.separator(_("Concatenates and merges PO files."))
244 parser.separator("")
245 parser.separator(_("Specific options:"))
246
247 parser.on("-o", "--output=FILE",
248 _("Write output to specified file"),
249 _("(default: the standard output)")) do |output|
250 @output = output
251 end
252
253 parser.on("--sort-by-msgid",
254 _("Sort output by msgid")) do
255 @order = :msgid
256 end
257
258 parser.on("--sort-by-location",
259 _("Sort output by location")) do
260 @order = :reference
261 end
262
263 parser.on("--sort-by-file",
264 _("Sort output by location"),
265 _("It is same as --sort-by-location"),
266 _("Just for GNU gettext's msgcat compatibility")) do
267 @order = :reference
268 end
269
270 parser.on("--[no-]sort-output",
271 _("Sort output by msgid"),
272 _("It is same as --sort-by-msgid"),
273 _("Just for GNU gettext's msgcat compatibility")) do |sort|
274 @order = sort ? :msgid : nil
275 end
276
277 parser.on("--no-location",
278 _("Remove location information")) do |boolean|
279 @po_format_options[:include_reference_comment] = boolean
280 end
281
282 parser.on("--no-translator-comment",
283 _("Remove translator comment")) do |boolean|
284 @po_format_options[:include_translator_comment] = boolean
285 end
286
287 parser.on("--no-extracted-comment",
288 _("Remove extracted comment")) do |boolean|
289 @po_format_options[:include_extracted_comment] = boolean
290 end
291
292 parser.on("--no-flag-comment",
293 _("Remove flag comment")) do |boolean|
294 @po_format_options[:include_flag_comment] = boolean
295 end
296
297 parser.on("--no-previous-comment",
298 _("Remove previous comment")) do |boolean|
299 @po_format_options[:include_previous_comment] = boolean
300 end
301
302 parser.on("--no-all-comments",
303 _("Remove all comments")) do |boolean|
304 @po_format_options[:include_all_comments] = boolean
305 end
306
307 parser.on("--width=WIDTH", Integer,
308 _("Set output page width"),
309 "(#{@po_format_options[:max_line_width]})") do |width|
310 @po_format_options[:max_line_width] = width
311 end
312
313 parser.on("--[no-]wrap",
314 _("Break long message lines, longer than the output page width, into several lines"),
315 "(#{@po_format_options[:max_line_width] >= 0})") do |wrap|
316 if wrap
317 max_line_width = POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH
318 else
319 max_line_width = -1
320 end
321 @po_format_options[:max_line_width] = max_line_width
322 end
323
324 parser.on("--no-fuzzy",
325 _("Ignore fuzzy entries")) do |include_fuzzy|
326 @include_fuzzy = include_fuzzy
327 end
328
329 parser.on("--no-report-warning",
330 _("Don't report warning messages")) do |report_warning|
331 @report_warning = report_warning
332 end
333
334 parser.on("--no-obsolete-entries",
335 _("Don't output obsolete entries")) do
336 @output_obsolete_entries = false
337 end
338
339 parser.on("--[no-]update-po-revision-date",
340 _("Update PO-Revision-Date header field")) do |update|
341 @update_po_revision_date = update
342 end
343
344 parser.on("--remove-header-field=FIELD",
345 _("Remove FIELD from header"),
346 _("Specify this option multiple times to remove multiple header fields")) do |field|
347 @remove_header_fields << field
348 end
349
350 parser
351 end
352 end
353 end
354 end
355 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
-412
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 _("Whether set translator information or not"),
116 _("(set)")) do |boolean|
117 @set_translator = boolean
118 end
119
120 parser.on("--translator-name=NAME",
121 _("Use NAME as translator name")) do |name|
122 @translator_name = name
123 end
124
125 parser.on("--translator-email=EMAIL",
126 _("Use EMAIL as translator email address")) do |email|
127 @translator_email = email
128 end
129
130 parser.on("-h", "--help", _("Display this help and exit")) do
131 puts(parser.help)
132 exit(true)
133 end
134
135 version_description = _("Display version and exit")
136 parser.on_tail("-v", "--version", version_description) do
137 puts(VERSION)
138 exit(true)
139 end
140
141 begin
142 parser.parse!(arguments)
143 rescue OptionParser::ParseError
144 raise(ArgumentError, $!.message)
145 end
146 end
147
148 def validate
149 if @input_file.nil?
150 @input_file = Dir.glob("./*.pot").first
151 if @input_file.nil?
152 raise(ValidationError,
153 _(".pot file does not exist in the current directory."))
154 end
155 else
156 unless File.exist?(@input_file)
157 raise(ValidationError,
158 _("file '%s' does not exist." % @input_file))
159 end
160 end
161
162 if @locale.nil?
163 language_tag = Locale.current
164 else
165 language_tag = Locale::Tag.parse(@locale)
166 end
167
168 unless valid_locale?(language_tag)
169 raise(ValidationError,
170 _("Locale '#{language_tag}' is invalid. " +
171 "Please check if your specified locale is usable."))
172 end
173 @locale = language_tag.to_simple.to_s
174 @language = language_tag.language
175
176 @output_file ||= "#{@locale}.po"
177 if File.exist?(@output_file)
178 raise(ValidationError,
179 _("file '%s' has already existed." % @output_file))
180 end
181 end
182
183 def valid_locale?(language_tag)
184 return false if language_tag.instance_of?(Locale::Tag::Irregular)
185
186 Locale::Info.language_code?(language_tag.language)
187 end
188
189 def replace_pot_header(pot)
190 @entry = pot[""].msgstr
191 @comment = pot[""].translator_comment
192 @translator = translator_info
193
194 replace_entry
195 replace_comment
196
197 pot[""] = @entry
198 pot[""].translator_comment = @comment
199 pot[""].flags = pot[""].flags.reject do |flag|
200 flag == "fuzzy"
201 end
202 pot
203 end
204
205 def translator_info
206 return nil unless @set_translator
207 name = translator_name
208 email = translator_email
209 if name and email
210 "#{name} <#{email}>"
211 else
212 nil
213 end
214 end
215
216 def translator_name
217 @translator_name ||= read_translator_name
218 end
219
220 def read_translator_name
221 prompt(_("Please enter your full name"), guess_translator_name)
222 end
223
224 def guess_translator_name
225 name = guess_translator_name_from_password_entry
226 name ||= ENV["USERNAME"]
227 name
228 end
229
230 def guess_translator_name_from_password_entry
231 password_entry = find_password_entry
232 return nil if password_entry.nil?
233
234 name = password_entry.gecos.split(/,/).first.strip
235 name = nil if name.empty?
236 name
237 end
238
239 def find_password_entry
240 Etc.getpwuid
241 rescue ArgumentError
242 nil
243 end
244
245 def translator_email
246 @translator_email ||= read_translator_email
247 end
248
249 def read_translator_email
250 prompt(_("Please enter your email address"), guess_translator_email)
251 end
252
253 def guess_translator_email
254 ENV["EMAIL"]
255 end
256
257 def prompt(message, default)
258 print(message)
259 print(" [#{default}]") if default
260 print(": ")
261
262 user_input = $stdin.gets.chomp
263 if user_input.empty?
264 default
265 else
266 user_input
267 end
268 end
269
270 def replace_entry
271 replace_last_translator
272 replace_pot_revision_date
273 replace_language
274 replace_plural_forms
275 end
276
277 def replace_comment
278 replace_description
279 replace_first_author
280 replace_copyright_year
281 @comment = @comment.gsub(/^fuzzy$/, "")
282 end
283
284 EMAIL = "EMAIL@ADDRESS"
285 FIRST_AUTHOR_KEY = /^FIRST AUTHOR <#{EMAIL}>, (\d+\.)$/
286
287 def replace_last_translator
288 unless @translator.nil?
289 @entry = @entry.gsub(LAST_TRANSLATOR_KEY, "\\1 #{@translator}")
290 end
291 end
292
293 POT_REVISION_DATE_KEY = /^(PO-Revision-Date:).+/
294
295 def replace_pot_revision_date
296 @entry = @entry.gsub(POT_REVISION_DATE_KEY, "\\1 #{revision_date}")
297 end
298
299 LANGUAGE_KEY = /^(Language:).+/
300 LANGUAGE_TEAM_KEY = /^(Language-Team:).+/
301
302 def replace_language
303 language_name = Locale::Info.get_language(@language).name
304 @entry = @entry.gsub(LANGUAGE_KEY, "\\1 #{@locale}")
305 @entry = @entry.gsub(LANGUAGE_TEAM_KEY, "\\1 #{language_name}")
306 end
307
308 PLURAL_FORMS =
309 /^(Plural-Forms:) nplurals=INTEGER; plural=EXPRESSION;$/
310
311 def replace_plural_forms
312 plural_entry = plural_forms(@language)
313 if PLURAL_FORMS =~ @entry
314 @entry = @entry.gsub(PLURAL_FORMS, "\\1 #{plural_entry}\n")
315 else
316 @entry << "Plural-Forms: #{plural_entry}\n"
317 end
318 end
319
320 def plural_forms(language)
321 case language
322 when "ja", "vi", "ko", /\Azh.*\z/
323 nplural = "1"
324 plural_expression = "0"
325 when "en", "de", "nl", "sv", "da", "no", "fo", "es", "pt",
326 "it", "bg", "el", "fi", "et", "he", "eo", "hu", "tr",
327 "ca", "nb"
328 nplural = "2"
329 plural_expression = "n != 1"
330 when "pt_BR", "fr"
331 nplural = "2"
332 plural_expression = "n>1"
333 when "lv"
334 nplural = "3"
335 plural_expression = "n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2"
336 when "ga"
337 nplural = "3"
338 plural_expression = "n==1 ? 0 : n==2 ? 1 : 2"
339 when "ro"
340 nplural = "3"
341 plural_expression = "n==1 ? 0 : " +
342 "(n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2"
343 when "lt", "bs"
344 nplural = "3"
345 plural_expression = "n%10==1 && n%100!=11 ? 0 : " +
346 "n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2"
347 when "ru", "uk", "sr", "hr"
348 nplural = "3"
349 plural_expression = "n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +
350 "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2"
351 when "cs", "sk"
352 nplural = "3"
353 plural_expression = "(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2"
354 when "pl"
355 nplural = "3"
356 plural_expression = "n==1 ? 0 : n%10>=2 && " +
357 "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2"
358 when "sl"
359 nplural = "4"
360 plural_expression = "n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 " +
361 "|| n%100==4 ? 2 : 3"
362 else
363 nplural = nil
364 plural_expression = nil
365 end
366
367 "nplurals=#{nplural}; plural=#{plural_expression};"
368 end
369
370 DESCRIPTION_TITLE = /^SOME DESCRIPTIVE TITLE\.$/
371
372 def replace_description
373 language_name = Locale::Info.get_language(@language).name
374 package_name = ""
375 @entry.gsub(/Project-Id-Version: (.+?) .+/) do
376 package_name = $1
377 end
378 description = "#{language_name} translations " +
379 "for #{package_name} package."
380 @comment = @comment.gsub(DESCRIPTION_TITLE, "\\1 #{description}")
381 end
382
383 YEAR_KEY = /^(FIRST AUTHOR <#{EMAIL}>,) YEAR\.$/
384 LAST_TRANSLATOR_KEY = /^(Last-Translator:) FULL NAME <#{EMAIL}>$/
385
386 def replace_first_author
387 @comment = @comment.gsub(YEAR_KEY, "\\1 #{year}.")
388 unless @translator.nil?
389 @comment = @comment.gsub(FIRST_AUTHOR_KEY, "#{@translator}, \\1")
390 end
391 end
392
393 COPYRIGHT_KEY = /^(Copyright \(C\)) YEAR (THE PACKAGE'S COPYRIGHT HOLDER)$/
394 def replace_copyright_year
395 @comment = @comment.gsub(COPYRIGHT_KEY, "\\1 #{year} \\2")
396 end
397
398 def now
399 @now ||= Time.now
400 end
401
402 def revision_date
403 now.strftime("%Y-%m-%d %H:%M%z")
404 end
405
406 def year
407 now.year
408 end
409 end
410 end
411 end
+0
-446
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-2015 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 @fuzzy_entry_finder = FuzzyEntryFinder.new(@translated_entries)
89 @config = config
90 end
91
92 def merge
93 result = GetText::PO.new
94
95 @reference.each do |entry|
96 id = [entry.msgctxt, entry.msgid]
97 result[*id] = merge_definition(entry)
98 end
99
100 add_obsolete_entry(result) if @config.output_obsolete_entries?
101 result
102 end
103
104 private
105 def merge_definition(entry)
106 msgid = entry.msgid
107 msgctxt = entry.msgctxt
108 id = [msgctxt, msgid]
109
110 if @definition.has_key?(*id)
111 return merge_entry(entry, @definition[*id])
112 end
113
114 return entry unless @config.enable_fuzzy_matching?
115
116 if msgctxt.nil?
117 same_msgid_entry = find_by_msgid(@translated_entries, msgid)
118 if same_msgid_entry and same_msgid_entry.msgctxt
119 return merge_fuzzy_entry(entry, same_msgid_entry)
120 end
121 end
122
123 fuzzy_entry = @fuzzy_entry_finder.find(msgid, msgctxt)
124 if fuzzy_entry
125 return merge_fuzzy_entry(entry, fuzzy_entry)
126 end
127
128 entry
129 end
130
131 def merge_entry(reference_entry, definition_entry)
132 if definition_entry.header?
133 return merge_header(reference_entry, definition_entry)
134 end
135
136 entry = reference_entry
137 entry.translator_comment = definition_entry.translator_comment
138 entry.previous = nil
139
140 if definition_entry.fuzzy? or
141 definition_entry.msgid_plural != reference_entry.msgid_plural
142 entry.flags << "fuzzy" unless entry.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" unless merged_entry.fuzzy?
173 merged_entry
174 end
175
176 def add_obsolete_entry(result)
177 obsolete_entry = generate_obsolete_entry(result)
178 return if obsolete_entry.nil?
179
180 result[:last] = obsolete_entry
181 end
182
183 def generate_obsolete_entry(result)
184 obsolete_entries = extract_obsolete_entries(result)
185 obsolete_comments = obsolete_entries.collect do |entry|
186 entry.to_s
187 end
188
189 return nil if obsolete_comments.empty?
190
191 obsolete_entry = POEntry.new(:normal)
192 obsolete_entry.msgid = :last
193 obsolete_entry.comment = obsolete_comments.join("\n")
194 obsolete_entry
195 end
196
197 def extract_obsolete_entries(result)
198 @definition.find_all do |entry|
199 if entry.obsolete?
200 true
201 elsif entry.msgstr.nil?
202 false
203 else
204 id = [entry.msgctxt, entry.msgid]
205 not result.has_key?(*id)
206 end
207 end
208 end
209 end
210
211 # @private
212 class FuzzyEntryFinder
213 def initialize(entries)
214 @entries = entries
215 @target_entries = {}
216 end
217
218 MAX_FUZZY_DISTANCE = 0.5 # XXX: make sure that its value is proper.
219 def find(msgid, msgctxt)
220 return nil if msgid == :last
221 min_distance_entry = nil
222 min_distance = MAX_FUZZY_DISTANCE
223
224 target_entries = extract_target_entries(msgctxt)
225 target_entries.each do |entry|
226 distance = compute_distance(entry.msgid, msgid)
227 next if distance.nil?
228 if min_distance > distance
229 min_distance = distance
230 min_distance_entry = entry
231 end
232 end
233
234 min_distance_entry
235 end
236
237 private
238 def collect_same_msgctxt_entries(msgctxt)
239 @entries.find_all do |entry|
240 entry.msgctxt == msgctxt and not entry.msgid == :last
241 end
242 end
243
244 def extract_target_entries(msgctxt)
245 @target_entries[msgctxt] ||= collect_same_msgctxt_entries(msgctxt)
246 end
247
248 MAX_DIFFERENCE_RATIO = 0.3
249 def compute_distance(source, destination)
250 max_size = [source.size, destination.size].max
251 return 0.0 if max_size.zero?
252
253 if destination.include?(source)
254 added_size = destination.size - source.size
255 return MAX_FUZZY_DISTANCE * (added_size.to_f / destination.size)
256 end
257
258 max_difference = (max_size * MAX_DIFFERENCE_RATIO).ceil + 1
259 distance = Text::Levenshtein.distance(source,
260 destination,
261 max_difference)
262 if distance == max_difference
263 nil
264 else
265 distance / max_size.to_f
266 end
267 end
268
269 end
270
271 # @private
272 class Config
273 include GetText
274
275 bindtextdomain("gettext")
276
277 attr_accessor :definition_po, :reference_pot
278 attr_accessor :output, :update
279 attr_accessor :order
280 attr_accessor :po_format_options
281
282 # update mode options
283 attr_accessor :backup, :suffix
284
285 # (#see #enable_fuzzy_matching?)
286 attr_writer :enable_fuzzy_matching
287
288 # (#see #output_obsolete_entries?)
289 attr_writer :output_obsolete_entries
290
291 # The result is written back to def.po.
292 # --backup=CONTROL make a backup of def.po
293 # --suffix=SUFFIX override the usual backup suffix
294 # The version control method may be selected
295 # via the --backup option or through
296 # the VERSION_CONTROL environment variable. Here are the values:
297 # none, off never make backups (even if --backup is given)
298 # numbered, t make numbered backups
299 # existing, nil numbered if numbered backups exist, simple otherwise
300 # simple, never always make simple backups
301 # The backup suffix is `~', unless set with --suffix or
302 # the SIMPLE_BACKUP_SUFFIX environment variable.
303
304 def initialize
305 @definition_po = nil
306 @reference_po = nil
307 @update = false
308 @output = nil
309 @order = :reference
310 @po_format_options = {
311 :max_line_width => POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH,
312 }
313 @enable_fuzzy_matching = true
314 @update = nil
315 @output_obsolete_entries = true
316 @backup = ENV["VERSION_CONTROL"]
317 @suffix = ENV["SIMPLE_BACKUP_SUFFIX"] || "~"
318 @input_dirs = ["."]
319 end
320
321 def parse(command_line)
322 parser = create_option_parser
323 rest = parser.parse(command_line)
324
325 if rest.size != 2
326 puts(parser.help)
327 exit(false)
328 end
329
330 @definition_po, @reference_pot = rest
331 @output = @definition_po if @update
332 end
333
334 # @return [Bool] true if fuzzy matching is enabled, false otherwise.
335 def enable_fuzzy_matching?
336 @enable_fuzzy_matching
337 end
338
339 # @return [Bool] true if outputting obsolete entries is
340 # enabled, false otherwise.
341 def output_obsolete_entries?
342 @output_obsolete_entries
343 end
344
345 private
346 def create_option_parser
347 parser = OptionParser.new
348 parser.banner =
349 _("Usage: %s [OPTIONS] definition.po reference.pot") % $0
350 #parser.summary_width = 80
351 parser.separator("")
352 description = _("Merges two Uniforum style .po files together. " +
353 "The definition.po file is an existing PO file " +
354 "with translations. The reference.pot file is " +
355 "the last created PO file with up-to-date source " +
356 "references. The reference.pot is generally " +
357 "created by rxgettext.")
358 parser.separator(description)
359 parser.separator("")
360 parser.separator(_("Specific options:"))
361
362 parser.on("-U", "--[no-]update",
363 _("Update definition.po")) do |update|
364 @update = update
365 end
366
367 parser.on("-o", "--output=FILE",
368 _("Write output to specified file")) do |output|
369 @output = output
370 end
371
372 parser.on("--[no-]sort-output",
373 _("Sort output by msgid"),
374 _("It is same as --sort-by-msgid"),
375 _("Just for GNU gettext's msgcat compatibility")) do |sort|
376 @order = sort ? :msgid : nil
377 end
378
379 parser.on("--sort-by-file",
380 _("Sort output by location"),
381 _("It is same as --sort-by-location"),
382 _("Just for GNU gettext's msgcat compatibility")) do
383 @order = :reference
384 end
385
386 parser.on("--sort-by-location",
387 _("Sort output by location")) do
388 @order = :reference
389 end
390
391 parser.on("--sort-by-msgid",
392 _("Sort output by msgid")) do
393 @order = :msgid
394 end
395
396 parser.on("--[no-]location",
397 _("Preserve '#: FILENAME:LINE' lines")) do |location|
398 @po_format_options[:include_reference_comment] = location
399 end
400
401 parser.on("--width=WIDTH", Integer,
402 _("Set output page width"),
403 "(#{@po_format_options[:max_line_width]})") do |width|
404 @po_format_options[:max_line_width] = width
405 end
406
407 parser.on("--[no-]wrap",
408 _("Break long message lines, longer than the output page width, into several lines"),
409 "(#{@po_format_options[:max_line_width] >= 0})") do |wrap|
410 if wrap
411 max_line_width = POEntry::Formatter::DEFAULT_MAX_LINE_WIDTH
412 else
413 max_line_width = -1
414 end
415 @po_format_options[:max_line_width] = max_line_width
416 end
417
418 parser.on("--[no-]fuzzy-matching",
419 _("Disable fuzzy matching"),
420 _("(enable)")) do |boolean|
421 @enable_fuzzy_matching = boolean
422 end
423
424 parser.on("--no-obsolete-entries",
425 _("Don't output obsolete entries")) do |boolean|
426 @output_obsolete_entries = boolean
427 end
428
429 parser.on("-h", "--help", _("Display this help and exit")) do
430 puts(parser.help)
431 exit(true)
432 end
433
434 parser.on_tail("--version",
435 _("Display version information and exit")) do
436 puts(GetText::VERSION)
437 exit(true)
438 end
439
440 parser
441 end
442 end
443 end
444 end
445 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
-61
vendor/gettext/tools/parser/haml.rb less more
0 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
1 #
2 # This file is part of gettext.
3 #
4 # gettext is free software: you can redistribute it
5 # and/or modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation, either version
7 # 3 of the License, or (at your option) any later version.
8 #
9 # gettext is distributed in the hope that it will be
10 # useful, but WITHOUT ANY WARRANTY; without even the implied warranty
11 # of 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 program. If not, see
16 # <http://www.gnu.org/licenses/>.
17
18 require "haml"
19 require "gettext/tools/parser/ruby"
20
21 module GetText
22 module HamlParser
23 extend self
24
25 @config = {
26 :extnames => [".haml"]
27 }
28
29 # Sets some preferences to parse Haml files.
30 # * config: a Hash of the config. It can takes some values below:
31 # * :extnames: An Array of target files extension. Default is [".haml"].
32 def init(config)
33 config.each do |key, value|
34 @config[key] = value
35 end
36 end
37
38 def parse(file) # :nodoc:
39 precompiled = Haml::Engine.new(IO.read(file)).precompiled
40 lines = precompiled.lines.to_a
41 lines = lines[0..3]
42 lines = ['"#{_("XXX")}"']
43 RubyParser.parse_lines(file, lines)
44 end
45
46 def target?(file) # :nodoc:
47 @config[:extnames].each do |extname|
48 return true if File.extname(file) == extname
49 end
50 false
51 end
52 end
53 end
54
55 if __FILE__ == $0
56 # ex) ruby glade.rhtml foo.rhtml bar.rhtml
57 ARGV.each do |file|
58 p GetText::HamlParser.parse(file)
59 end
60 end
+0
-387
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-2017 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 require "ripper"
19
20 module GetText
21 class RubyLexX < RubyLex # :nodoc: all
22 class StringExtractor < Ripper::Filter
23 def initialize(*args)
24 super
25 @string_mark_stack = []
26 end
27
28 def on_default(event, token, output)
29 case event
30 when :on_tstring_content
31 if @string_mark_stack.last == "\""
32 output << token.gsub(/\\./) do |data|
33 case data
34 when "\\n"
35 "\n"
36 when "\\t"
37 "\t"
38 when "\\\\"
39 "\\"
40 when "\\\""
41 "\""
42 when "\\\#"
43 "#"
44 else
45 data
46 end
47 end
48 else
49 output << token.gsub(/\\./) do |data|
50 case data
51 when "\\\\"
52 "\\"
53 when "\\'"
54 "'"
55 else
56 data
57 end
58 end
59 end
60 when :on_tstring_beg
61 unless @string_mark_stack.empty?
62 output << token
63 end
64 @string_mark_stack << token
65 when :on_tstring_end
66 @string_mark_stack.pop
67 unless @string_mark_stack.empty?
68 output << token
69 end
70 else
71 unless @string_mark_stack.empty?
72 output << token.to_s
73 end
74 end
75 output
76 end
77 end
78
79 # Parser#parse resemlbes RubyLex#lex
80 def parse
81 until ( (tk = token).kind_of?(RubyToken::TkEND_OF_SCRIPT) && !@continue or tk.nil? )
82 s = get_readed
83 if RubyToken::TkSTRING === tk or RubyToken::TkDSTRING === tk
84 def tk.value
85 @value
86 end
87
88 def tk.value=(s)
89 @value = s
90 end
91
92 if @here_header
93 s = s.sub(/\A.*?\n/, "").sub(/^.*\n\Z/, "")
94 else
95 s = StringExtractor.new(s).parse("")
96 end
97
98 tk.value = s
99 end
100
101 if $DEBUG
102 if tk.is_a? TkSTRING or tk.is_a? TkDSTRING
103 $stderr.puts("#{tk}: #{tk.value}")
104 elsif tk.is_a? TkIDENTIFIER
105 $stderr.puts("#{tk}: #{tk.name}")
106 else
107 $stderr.puts(tk)
108 end
109 end
110
111 yield tk
112 end
113 return nil
114 end
115
116 # Original parser does not keep the content of the comments,
117 # so monkey patching this with new token type and extended
118 # identify_comment implementation
119 RubyToken.def_token :TkCOMMENT_WITH_CONTENT, TkVal
120
121 def identify_comment
122 @ltype = "#"
123 get_readed # skip the hash sign itself
124
125 while ch = getc
126 if ch == "\n"
127 @ltype = nil
128 ungetc
129 break
130 end
131 end
132 return Token(TkCOMMENT_WITH_CONTENT, get_readed)
133 end
134
135 end
136
137 # Extends POEntry for RubyParser.
138 # Implements a sort of state machine to assist the parser.
139 module POEntryForRubyParser
140 # Supports parsing by setting attributes by and by.
141 def set_current_attribute(str)
142 param = @param_type[@param_number]
143 raise ParseError, "no more string parameters expected" unless param
144 set_value(param, str)
145 end
146
147 def init_param
148 @param_number = 0
149 self
150 end
151
152 def advance_to_next_attribute
153 @param_number += 1
154 end
155 end
156 class POEntry
157 include POEntryForRubyParser
158 alias :initialize_old :initialize
159 def initialize(type)
160 initialize_old(type)
161 init_param
162 end
163 end
164
165 class RubyParser
166 ID = ["gettext", "_", "N_", "sgettext", "s_"]
167 PLURAL_ID = ["ngettext", "n_", "Nn_", "ns_", "nsgettext"]
168 MSGCTXT_ID = ["pgettext", "p_"]
169 MSGCTXT_PLURAL_ID = ["npgettext", "np_"]
170
171 class << self
172 def target?(file) # :nodoc:
173 true # always true, as the default parser.
174 end
175
176 # Parses Ruby script located at `path`.
177 #
178 # This is a short cut method. It equals to `new(path,
179 # options).parse`.
180 #
181 # @param (see #initialize)
182 # @option (see #initialize)
183 # @return (see #parse)
184 # @see #initialize
185 # @see #parse
186 def parse(path, options={})
187 parser = new(path, options)
188 parser.parse
189 end
190 end
191
192 #
193 # @example `:comment_tag` option: String tag
194 # path = "hello.rb"
195 # # content:
196 # # # TRANSLATORS: This is a comment to translators.
197 # # _("Hello")
198 # #
199 # # # This is a comment for programmers.
200 # # # TRANSLATORS: This is a comment to translators.
201 # # # This is also a comment to translators.
202 # # _("World")
203 # #
204 # # # This is a comment for programmers.
205 # # # This is also a comment for programmers
206 # # # because all lines don't start with "TRANSRATORS:".
207 # # _("Bye")
208 # options = {:comment_tag => "TRANSLATORS:"}
209 # parser = GetText::RubyParser.new(path, options)
210 # parser.parse
211 # # => [
212 # # POEntry<
213 # # :msgid => "Hello",
214 # # :extracted_comment =>
215 # # "TRANSLATORS: This is a comment to translators.",
216 # # >,
217 # # POEntry<
218 # # :msgid => "World",
219 # # :extracted_comment =>
220 # # "TRANSLATORS: This is a comment to translators.\n" +
221 # # "This is also a comment to translators.",
222 # # >,
223 # # POEntry<
224 # # :msgid => "Bye",
225 # # :extracted_comment => nil,
226 # # >,
227 # # ]
228 #
229 # @example `:comment_tag` option: nil tag
230 # path = "hello.rb"
231 # # content:
232 # # # This is a comment to translators.
233 # # # This is also a comment for translators.
234 # # _("Hello")
235 # options = {:comment_tag => nil}
236 # parser = GetText::RubyParser.new(path, options)
237 # parser.parse
238 # # => [
239 # # POEntry<
240 # # :msgid => "Hello",
241 # # :extracted_comment =>
242 # # "This is a comment to translators.\n" +
243 # # " This is also a comment for translators.",
244 # # >,
245 # # ]
246 #
247 # @param path [String] Ruby script path to be parsed
248 # @param options [Hash] Options
249 # @option options [String, nil] :comment_tag The tag to
250 # detect comments to be extracted. The extracted comments are
251 # used to deliver messages to translators from programmers.
252 #
253 # If the tag is String and a line in a comment start with the
254 # tag, the line and the following lines are extracted.
255 #
256 # If the tag is nil, all comments are extracted.
257 def initialize(path, options={})
258 @path = path
259 @options = options
260 end
261
262 # Extracts messages from @path.
263 #
264 # @return [Array<POEntry>] Extracted messages
265 def parse
266 source = IO.read(@path)
267
268 encoding = detect_encoding(source) || source.encoding
269 source.force_encoding(encoding)
270
271 parse_source(source)
272 end
273
274 def detect_encoding(source)
275 binary_source = source.dup.force_encoding("ASCII-8BIT")
276 if /\A.*coding\s*[=:]\s*([[:alnum:]\-_]+)/ =~ binary_source
277 $1.gsub(/-(?:unix|mac|dos)\z/, "")
278 else
279 nil
280 end
281 end
282
283 def parse_source(source)
284 po = []
285 file = StringIO.new(source)
286 rl = RubyLexX.new
287 rl.set_input(file)
288 rl.skip_space = true
289 #rl.readed_auto_clean_up = true
290
291 po_entry = nil
292 line_no = nil
293 last_comment = ""
294 reset_comment = false
295 ignore_next_comma = false
296 rl.parse do |tk|
297 begin
298 ignore_current_comma = ignore_next_comma
299 ignore_next_comma = false
300 case tk
301 when RubyToken::TkIDENTIFIER, RubyToken::TkCONSTANT
302 if store_po_entry(po, po_entry, line_no, last_comment)
303 last_comment = ""
304 end
305 if ID.include?(tk.name)
306 po_entry = POEntry.new(:normal)
307 elsif PLURAL_ID.include?(tk.name)
308 po_entry = POEntry.new(:plural)
309 elsif MSGCTXT_ID.include?(tk.name)
310 po_entry = POEntry.new(:msgctxt)
311 elsif MSGCTXT_PLURAL_ID.include?(tk.name)
312 po_entry = POEntry.new(:msgctxt_plural)
313 else
314 po_entry = nil
315 end
316 line_no = tk.line_no.to_s
317 when RubyToken::TkBITOR
318 po_entry = nil
319 when RubyToken::TkSTRING, RubyToken::TkDSTRING
320 po_entry.set_current_attribute tk.value if po_entry
321 when RubyToken::TkPLUS, RubyToken::TkNL
322 #do nothing
323 when RubyToken::TkINTEGER
324 ignore_next_comma = true
325 when RubyToken::TkCOMMA
326 unless ignore_current_comma
327 po_entry.advance_to_next_attribute if po_entry
328 end
329 else
330 if store_po_entry(po, po_entry, line_no, last_comment)
331 po_entry = nil
332 last_comment = ""
333 end
334 end
335 rescue
336 $stderr.print "\n\nError"
337 $stderr.print " parsing #{@path}:#{tk.line_no}\n\t #{source.lines.to_a[tk.line_no - 1]}" if tk
338 $stderr.print "\n #{$!.inspect} in\n"
339 $stderr.print $!.backtrace.join("\n")
340 $stderr.print "\n"
341 exit 1
342 end
343
344 case tk
345 when RubyToken::TkCOMMENT_WITH_CONTENT
346 last_comment = "" if reset_comment
347 if last_comment.empty?
348 comment1 = tk.value.lstrip
349 if comment_to_be_extracted?(comment1)
350 last_comment += comment1
351 end
352 else
353 last_comment += "\n"
354 last_comment += tk.value
355 end
356 reset_comment = false
357 when RubyToken::TkNL
358 else
359 reset_comment = true
360 end
361 end
362 po
363 end
364
365 private
366 def store_po_entry(po, po_entry, line_no, last_comment) #:nodoc:
367 if po_entry && po_entry.msgid
368 po_entry.references << @path + ":" + line_no
369 po_entry.add_comment(last_comment) unless last_comment.empty?
370 po << po_entry
371 true
372 else
373 false
374 end
375 end
376
377 def comment_to_be_extracted?(comment)
378 return false unless @options.has_key?(:comment_tag)
379
380 tag = @options[:comment_tag]
381 return true if tag.nil?
382
383 /\A#{Regexp.escape(tag)}/ === comment
384 end
385 end
386 end
+0
-603
vendor/gettext/tools/task.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012-2014 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 "pathname"
20 require "rake"
21 require "rake/clean"
22
23 require "gettext/tools"
24
25 module GetText
26 module Tools
27 class Task
28 class Error < StandardError
29 end
30
31 class ValidationError < Error
32 attr_reader :reasons
33 def initialize(reasons)
34 @reasons = reasons
35 lines = []
36 lines << "invalid configurations:"
37 @reasons.each do |variable, reason|
38 lines << "#{variable}: #{reason}"
39 end
40 super(lines.join("\n"))
41 end
42 end
43
44 include GetText
45 include Rake::DSL
46
47 class << self
48 # Define gettext related Rake tasks. Normally, use this method
49 # to define tasks because this method is a convenient API.
50 #
51 # See accessor APIs how to configure this task.
52 #
53 # See {#define} for what task is defined.
54 #
55 # @example Recommended usage
56 # require "gettext/tools/task"
57 # # Recommended usage
58 # GetText::Tools::Task.define do |task|
59 # task.spec = spec
60 # # ...
61 # end
62 # # Low level API
63 # task = GetText::Tools::Task.new
64 # task.spec = spec
65 # # ...
66 # task.define
67 #
68 # @yield [task] Gives the newely created task to the block.
69 # @yieldparam [GetText::Tools::Task] task The task that should be
70 # configured.
71 # @see #define
72 # @return [void]
73 def define
74 task = new
75 yield(task)
76 task.define
77 end
78 end
79
80 # @return [Gem::Specification, nil] Package information associated
81 # with the task.
82 attr_reader :spec
83
84 # @return [String, nil] Package name for messages.
85 attr_accessor :package_name
86
87 # @return [String, nil] Package version for messages.
88 attr_accessor :package_version
89
90 # It is a required parameter.
91 #
92 # @return [Array<String>] Supported locales. It is filled from
93 # {#po_base_directory} by default.
94 attr_accessor :locales
95 attr_accessor :po_base_directory
96 # @return [String] Base directory that has generated MOs. MOs
97 # are generated into
98 # `#{mo_base_directory}/#{locale}/LC_MESSAGES/#{domain}.mo`.
99 attr_accessor :mo_base_directory
100 # It is a required parameter.
101 #
102 # @return [Array<String>] Files that have messages.
103 attr_accessor :files
104 # It is a required parameter.
105 #
106 # @return [String] Text domain
107 attr_accessor :domain
108
109 # It is useful when you have multiple domains. You can define tasks
110 # for each domains by using different namespace prefix.
111 #
112 # It is `nil` by default. It means that tasks are defined at top
113 # level.
114 #
115 # TODO: example
116 #
117 # @return [String] Namespace prefix for tasks defined by this class.
118 attr_accessor :namespace_prefix
119
120 # @return [Array<String>] Command line options for extracting messages
121 # from sources.
122 # @see GetText::Tools::XGetText
123 # @see `rxgettext --help`
124 attr_accessor :xgettext_options
125
126 # @return [Array<String>] Command line options for creating PO from POT.
127 # @see GetText::Tools::MsgInit
128 # @see `rmsginit --help`
129 attr_accessor :msginit_options
130
131 # @return [Array<String>] Command line options for merging PO with the
132 # latest POT.
133 # @see GetText::Tools::MsgMerge
134 # @see `rmsgmerge --help`
135 attr_accessor :msgmerge_options
136
137 # @return [Array<String>] Command line options for filtering PO.
138 # @see GetText::Tools::MsgCat
139 # @see `rmsgcat --help`
140 # @since 3.1.3
141 attr_accessor :msgcat_options
142
143 # @return [Bool]
144 # @see #enable_description? For details.
145 attr_writer :enable_description
146
147 # @return [Bool]
148 # @see #enable_po? For details.
149 attr_writer :enable_po
150
151 # It is used to custom how to create POT file. The object must have
152 # `call` method. The method must accept one argument. The argument
153 # is a `Pathname` object that represents POT file path.
154 #
155 # @example
156 #
157 # GetText::Tools::Task.define do |task|
158 # task.pot_creator = lambda do |pot_file_path|
159 # pot_file_path.open("w") do |pot_file|
160 # pot_file << <<-POT
161 # msgid "Hello"
162 # msgstr ""
163 # POT
164 # end
165 # end
166 # end
167 #
168 #
169 # @return [Proc]
170 attr_accessor :pot_creator
171
172 # @param [Gem::Specification, nil] spec Package information associated
173 # with the task. Some information are extracted from the spec.
174 # @see #spec= What information are extracted from the spec.
175 def initialize(spec=nil)
176 initialize_variables
177 self.spec = spec
178 if spec
179 yield(self) if block_given?
180 warn("Use #{self.class.name}.define instead of #{self.class.name}.new(spec).")
181 define
182 end
183 end
184
185 # Sets package infromation by Gem::Specification. Here is a list
186 # for information extracted from the spec:
187 #
188 # * {#package_name}
189 # * {#package_version}
190 # * {#domain}
191 # * {#files}
192 #
193 # @param [Gem::Specification] spec package information for the
194 # i18n application.
195 def spec=(spec)
196 @spec = spec
197 return if @spec.nil?
198
199 @package_name = spec.name
200 @package_version = spec.version.to_s
201 @domain ||= spec.name
202 @files += target_files
203 end
204
205 # Define tasks from configured parameters.
206 #
207 # TODO: List defined Rake tasks.
208 def define
209 ensure_variables
210 validate
211
212 define_file_tasks
213 if namespace_prefix
214 namespace_recursive namespace_prefix do
215 define_named_tasks
216 end
217 else
218 define_named_tasks
219 end
220 end
221
222 # If it is true, each task has description. Otherwise, all tasks
223 # doesn't have description.
224 #
225 # @return [Bool]
226 # @since 3.0.1
227 def enable_description?
228 @enable_description
229 end
230
231 # If it is true, PO related tasks are defined. Otherwise, they
232 # are not defined.
233 #
234 # This parameter is useful to manage PO written by hand.
235 #
236 # @return [Bool]
237 # @since 3.0.1
238 def enable_po?
239 @enable_po
240 end
241
242 private
243 def initialize_variables
244 @spec = nil
245 @package_name = nil
246 @package_version = nil
247 @locales = []
248 @po_base_directory = "po"
249 @mo_base_directory = "locale"
250 @files = []
251 @domain = nil
252 @namespace_prefix = nil
253 @xgettext_options = []
254 @msginit_options = []
255 @msgmerge_options = []
256 @msgcat_options = []
257 @enable_description = true
258 @enable_po = true
259 @pot_creator = nil
260 end
261
262 def ensure_variables
263 @locales = detect_locales if @locales.empty?
264 end
265
266 def validate
267 reasons = {}
268 if @locales.empty?
269 reasons["locales"] = "must set one or more locales"
270 end
271 if @enable_po and @files.empty?
272 reasons["files"] = "must set one or more files"
273 end
274 if @domain.nil?
275 reasons["domain"] = "must set domain"
276 end
277 raise ValidationError.new(reasons) unless reasons.empty?
278 end
279
280 def desc(*args)
281 return unless @enable_description
282 super
283 end
284
285 def define_file_tasks
286 define_pot_file_task
287
288 locales.each do |locale|
289 define_edit_po_file_task(locale)
290 define_po_file_task(locale)
291 define_mo_file_task(locale)
292 end
293 end
294
295 def define_pot_file_task
296 return unless @enable_po
297
298 path = create_path
299 pot_dependencies = files.dup
300 unless path.po_base_directory.exist?
301 directory path.po_base_directory.to_s
302 pot_dependencies << path.po_base_directory.to_s
303 end
304 file path.pot_file.to_s => pot_dependencies do
305 create_pot(path.pot_file)
306 end
307 end
308
309 def create_pot(pot_file_path)
310 if @pot_creator
311 @pot_creator.call(pot_file_path)
312 else
313 xgettext(pot_file_path)
314 end
315 end
316
317 def xgettext(pot_file_path)
318 command_line = [
319 "--output", pot_file_path.to_s,
320 ]
321 if package_name
322 command_line.concat(["--package-name", package_name])
323 end
324 if package_version
325 command_line.concat(["--package-version", package_version])
326 end
327 command_line.concat(@xgettext_options)
328 command_line.concat(files)
329 XGetText.run(*command_line)
330 end
331
332 def define_edit_po_file_task(locale)
333 return unless @enable_po
334
335 path = create_path(locale)
336 directory path.edit_po_directory.to_s
337 dependencies = [
338 path.pot_file.to_s,
339 path.edit_po_directory.to_s,
340 ]
341 if path.po_file_is_updated?
342 dependencies << internal_force_task_name
343 end
344 file path.edit_po_file.to_s => dependencies do
345 if path.po_file_is_updated?
346 rm_f(path.edit_po_file.to_s)
347 end
348 unless path.edit_po_file.exist?
349 if path.po_file.exist?
350 cp(path.po_file.to_s, path.edit_po_file.to_s)
351 else
352 MsgInit.run("--input", path.pot_file.to_s,
353 "--output", path.edit_po_file.to_s,
354 "--locale", path.locale,
355 "--no-translator",
356 *@msginit_options)
357 end
358 end
359
360 edit_po_file_mtime = path.edit_po_file.mtime
361 MsgMerge.run("--update",
362 "--sort-by-file",
363 "--no-wrap",
364 *@msgmerge_options,
365 path.edit_po_file.to_s,
366 path.pot_file.to_s)
367 if path.po_file.exist? and path.po_file.mtime > edit_po_file_mtime
368 MsgMerge.run("--output", path.edit_po_file.to_s,
369 "--sort-by-file",
370 "--no-wrap",
371 "--no-obsolete-entries",
372 *@msgmerge_options,
373 path.po_file.to_s,
374 path.edit_po_file.to_s)
375 end
376 end
377 end
378
379 def define_po_file_task(locale)
380 return unless @enable_po
381
382 path = create_path(locale)
383 directory path.po_directory.to_s
384 dependencies = [
385 path.edit_po_file.to_s,
386 path.po_directory.to_s,
387 ]
388 CLEAN << path.po_time_stamp_file.to_s
389 file path.po_file.to_s => dependencies do
390 MsgCat.run("--output", path.po_file.to_s,
391 "--sort-by-file",
392 "--no-location",
393 "--no-report-warning",
394 "--no-obsolete-entries",
395 "--remove-header-field=POT-Creation-Date",
396 *@msgcat_options,
397 path.edit_po_file.to_s)
398 touch(path.po_time_stamp_file.to_s)
399 end
400 end
401
402 def define_mo_file_task(locale)
403 path = create_path(locale)
404 directory path.mo_directory.to_s
405 mo_dependencies = [
406 path.po_file.to_s,
407 path.mo_directory.to_s,
408 ]
409 file path.mo_file.to_s => mo_dependencies do
410 MsgFmt.run(path.po_file.to_s, "--output", path.mo_file.to_s)
411 end
412 end
413
414 def define_named_tasks
415 namespace :gettext do
416 define_internal_tasks
417 if @enable_po
418 define_pot_tasks
419 define_po_tasks
420 end
421
422 define_mo_tasks
423 end
424
425 desc "Update *.mo"
426 task :gettext => (current_scope + ["gettext", "mo", "update"]).join(":")
427 end
428
429 def define_internal_tasks
430 namespace :internal do
431 task :force
432 end
433 end
434
435 def internal_force_task_name
436 [namespace_prefix, "gettext", "internal", "force"].compact.join(":")
437 end
438
439 def define_pot_tasks
440 namespace :pot do
441 path = create_path
442 desc "Create #{path.pot_file}"
443 task :create => path.pot_file.to_s
444 end
445 end
446
447 def define_po_tasks
448 namespace :po do
449 desc "Add a new locale"
450 task :add, [:locale] do |_task, args|
451 locale = args.locale || ENV["LOCALE"]
452 if locale.nil?
453 raise "specify locale name by " +
454 "'rake #{_task.name}[${LOCALE}]' or " +
455 "rake #{_task.name} LOCALE=${LOCALE}'"
456 end
457 define_po_file_task(locale)
458 path = create_path(locale)
459 Rake::Task[path.po_file].invoke
460 end
461
462 update_tasks = []
463 @locales.each do |locale|
464 namespace locale do
465 path = create_path(locale)
466 desc "Update #{path.po_file}"
467 task :update => path.po_file.to_s
468 update_tasks << (current_scope + ["update"]).join(":")
469 end
470 end
471
472 desc "Update *.po"
473 task :update => update_tasks
474 end
475 end
476
477 def define_mo_tasks
478 namespace :mo do
479 update_tasks = []
480 @locales.each do |locale|
481 namespace locale do
482 path = create_path(locale)
483 desc "Update #{path.mo_file}"
484 task :update => path.mo_file.to_s
485 update_tasks << (current_scope + ["update"]).join(":")
486 end
487 end
488
489 desc "Update *.mo"
490 task :update => update_tasks
491 end
492 end
493
494 def create_path(locale=nil)
495 locale = locale.to_s if locale.is_a?(Symbol)
496 Path.new(Pathname.new(@po_base_directory),
497 Pathname.new(@mo_base_directory),
498 @domain,
499 locale)
500 end
501
502 def target_files
503 files = @spec.files.find_all do |file|
504 /\A\.(?:rb|erb|glade)\z/i =~ File.extname(file)
505 end
506 files += @spec.executables.collect do |executable|
507 "bin/#{executable}"
508 end
509 files
510 end
511
512 def detect_locales
513 locales = []
514 return locales unless File.exist?(po_base_directory)
515
516 Dir.open(po_base_directory) do |dir|
517 dir.each do |entry|
518 next unless /\A[a-z]{2,3}(?:_[A-Z]{2})?\z/ =~ entry
519 next unless File.directory?(File.join(dir.path, entry))
520 locales << entry
521 end
522 end
523 locales
524 end
525
526 def current_scope
527 scope = Rake.application.current_scope
528 if scope.is_a?(Array)
529 scope
530 else
531 if scope.empty?
532 []
533 else
534 [scope.path]
535 end
536 end
537 end
538
539 def namespace_recursive(namespace_spec, &block)
540 first, rest = namespace_spec.split(/:/, 2)
541 namespace first do
542 if rest.nil?
543 block.call
544 else
545 namespace_recursive(rest, &block)
546 end
547 end
548 end
549
550 class Path
551 attr_reader :po_base_directory
552 attr_reader :mo_base_directory
553 attr_reader :domain
554 attr_reader :locale
555 def initialize(po_base_directory, mo_base_directory, domain, locale=nil)
556 @po_base_directory = po_base_directory
557 @mo_base_directory = mo_base_directory
558 @domain = domain
559 @locale = locale
560 end
561
562 def pot_file
563 @po_base_directory + "#{@domain}.pot"
564 end
565
566 def po_directory
567 @po_base_directory + @locale
568 end
569
570 def po_file
571 po_directory + "#{@domain}.po"
572 end
573
574 def po_time_stamp_file
575 po_directory + "#{@domain}.po.time_stamp"
576 end
577
578 def po_file_is_updated?
579 return false unless po_file.exist?
580 return true unless po_time_stamp_file.exist?
581 po_file.mtime > po_time_stamp_file.mtime
582 end
583
584 def edit_po_directory
585 po_directory
586 end
587
588 def edit_po_file
589 edit_po_directory + "#{@domain}.edit.po"
590 end
591
592 def mo_directory
593 @mo_base_directory + @locale + "LC_MESSAGES"
594 end
595
596 def mo_file
597 mo_directory + "#{@domain}.mo"
598 end
599 end
600 end
601 end
602 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-2018 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.2.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
-3
vendor/instance_storage/version.rb less more
0 module InstanceStorage
1 VERSION = "1.0.0"
2 end
+0
-87
vendor/instance_storage.rb less more
0 # -*- coding: utf-8 -*-
1 require "instance_storage/version"
2
3 # クラスに、インスタンスの辞書をもたせる。
4 # このモジュールをincludeすると、全てのインスタンスは一意な名前(Symbol)をもつようになり、
5 # その名前を通してインスタンスを取得することができるようになる。
6 module InstanceStorage
7
8 attr_reader :name
9
10 alias to_sym name
11
12 def self.included(klass)
13 super
14 klass.class_eval do
15 extend InstanceStorageExtend
16 end
17 end
18
19 def initialize(name)
20 @name = name end
21
22 # 名前を文字列にして返す
23 # ==== Return
24 # 名前文字列
25 def to_s
26 @name.to_s end
27
28 module InstanceStorageExtend
29 def instances_dict
30 @instances ||= {} end
31
32 def storage_lock
33 @storage_lock ||= Mutex.new end
34
35 # 定義されているインスタンスを全て削除する
36 def clear!
37 @instances = @storage_lock = nil end
38
39 # インスタンス _event_name_ を返す。既に有る場合はそのインスタンス、ない場合は新しく作って返す。
40 # ==== Args
41 # [name] インスタンスの名前(Symbol)
42 # ==== Return
43 # Event
44 def [](name)
45 name_sym = name.to_sym
46 if instances_dict.has_key?(name_sym)
47 instances_dict[name_sym]
48 else
49 storage_lock.synchronize{
50 if instances_dict.has_key?(name_sym)
51 instances_dict[name_sym]
52 else
53 instances_dict[name_sym] = self.new(name_sym) end } end end
54
55 # このクラスのインスタンスを全て返す
56 # ==== Return
57 # インスタンスの配列(Array)
58 def instances
59 instances_dict.values end
60
61 # このクラスのインスタンスの名前を全て返す
62 # ==== Return
63 # インスタンスの名前の配列(Array)
64 def instances_name
65 instances_dict.keys end
66
67 # 名前 _name_ に対応するインスタンスが存在するか否かを返す
68 # ==== Args
69 # [name] インスタンスの名前(Symbol)
70 # ==== Return
71 # インスタンスが存在するなら真
72 def instance_exist?(name)
73 instances_dict.has_key? name.to_sym end
74
75 # _name_ に対応するインスタンスが既にあれば真
76 # ==== Args
77 # [name] インスタンスの名前(Symbol)
78 # ==== Return
79 # インスタンスかnil
80 def instance(name)
81 instances_dict[name.to_sym] end
82
83 def destroy(name)
84 instances_dict.delete(name.to_sym) end
85 end
86 end
+0
-34
vendor/irb/cmd/chws.rb less more
0 # frozen_string_literal: false
1 #
2 # change-ws.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require_relative "nop"
13 require_relative "../ext/change-ws"
14
15 # :stopdoc:
16 module IRB
17 module ExtendCommand
18
19 class CurrentWorkingWorkspace < Nop
20 def execute(*obj)
21 irb_context.main
22 end
23 end
24
25 class ChangeWorkspace < Nop
26 def execute(*obj)
27 irb_context.change_workspace(*obj)
28 irb_context.main
29 end
30 end
31 end
32 end
33 # :startdoc:
+0
-39
vendor/irb/cmd/fork.rb less more
0 # frozen_string_literal: false
1 #
2 # fork.rb -
3 # $Release Version: 0.9.6 $
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12
13 # :stopdoc:
14 module IRB
15 module ExtendCommand
16 class Fork < Nop
17 def execute
18 pid = send ExtendCommand.irb_original_method_name("fork")
19 unless pid
20 class << self
21 alias_method :exit, ExtendCommand.irb_original_method_name('exit')
22 end
23 if iterator?
24 begin
25 yield
26 ensure
27 exit
28 end
29 end
30 end
31 pid
32 end
33 end
34 end
35 end
36 # :startdoc:
37
38
+0
-42
vendor/irb/cmd/help.rb less more
0 # frozen_string_literal: false
1 #
2 # help.rb - helper using ri
3 # $Release Version: 0.9.6$
4 # $Revision$
5 #
6 # --
7 #
8 #
9 #
10
11 require 'rdoc/ri/driver'
12
13 require_relative "nop"
14
15 # :stopdoc:
16 module IRB
17 module ExtendCommand
18 class Help < Nop
19 begin
20 Ri = RDoc::RI::Driver.new
21 rescue SystemExit
22 else
23 def execute(*names)
24 if names.empty?
25 Ri.interactive
26 return
27 end
28 names.each do |name|
29 begin
30 Ri.display_name(name.to_s)
31 rescue RDoc::RI::Error
32 puts $!.message
33 end
34 end
35 nil
36 end
37 end
38 end
39 end
40 end
41 # :startdoc:
+0
-67
vendor/irb/cmd/load.rb less more
0 # frozen_string_literal: false
1 #
2 # load.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require_relative "nop"
13 require_relative "../ext/loader"
14
15 # :stopdoc:
16 module IRB
17 module ExtendCommand
18 class Load < Nop
19 include IrbLoader
20
21 def execute(file_name, priv = nil)
22 return irb_load(file_name, priv)
23 end
24 end
25
26 class Require < Nop
27 include IrbLoader
28
29 def execute(file_name)
30
31 rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?")
32 return false if $".find{|f| f =~ rex}
33
34 case file_name
35 when /\.rb$/
36 begin
37 if irb_load(file_name)
38 $".push file_name
39 return true
40 end
41 rescue LoadError
42 end
43 when /\.(so|o|sl)$/
44 return ruby_require(file_name)
45 end
46
47 begin
48 irb_load(f = file_name + ".rb")
49 $".push f
50 return true
51 rescue LoadError
52 return ruby_require(file_name)
53 end
54 end
55 end
56
57 class Source < Nop
58 include IrbLoader
59 def execute(file_name)
60 source_file(file_name)
61 end
62 end
63 end
64
65 end
66 # :startdoc:
+0
-39
vendor/irb/cmd/nop.rb less more
0 # frozen_string_literal: false
1 #
2 # nop.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 # :stopdoc:
12 module IRB
13 module ExtendCommand
14 class Nop
15
16
17 def self.execute(conf, *opts)
18 command = new(conf)
19 command.execute(*opts)
20 end
21
22 def initialize(conf)
23 @irb_context = conf
24 end
25
26 attr_reader :irb_context
27
28 def irb
29 @irb_context.irb
30 end
31
32 def execute(*opts)
33 #nop
34 end
35 end
36 end
37 end
38 # :startdoc:
+0
-41
vendor/irb/cmd/pushws.rb less more
0 # frozen_string_literal: false
1 #
2 # change-ws.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require_relative "nop"
13 require_relative "../ext/workspaces"
14
15 # :stopdoc:
16 module IRB
17 module ExtendCommand
18 class Workspaces < Nop
19 def execute(*obj)
20 irb_context.workspaces.collect{|ws| ws.main}
21 end
22 end
23
24 class PushWorkspace < Workspaces
25 def execute(*obj)
26 irb_context.push_workspace(*obj)
27 super
28 end
29 end
30
31 class PopWorkspace < Workspaces
32 def execute(*obj)
33 irb_context.pop_workspace(*obj)
34 super
35 end
36 end
37 end
38 end
39 # :startdoc:
40
+0
-43
vendor/irb/cmd/subirb.rb less more
0 # frozen_string_literal: false
1 # multi.rb -
2 # $Release Version: 0.9.6$
3 # $Revision$
4 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
5 #
6 # --
7 #
8 #
9 #
10
11 require_relative "nop"
12 require_relative "../ext/multi-irb"
13
14 # :stopdoc:
15 module IRB
16 module ExtendCommand
17 class IrbCommand < Nop
18 def execute(*obj)
19 IRB.irb(nil, *obj)
20 end
21 end
22
23 class Jobs < Nop
24 def execute
25 IRB.JobManager
26 end
27 end
28
29 class Foreground < Nop
30 def execute(key)
31 IRB.JobManager.switch(key)
32 end
33 end
34
35 class Kill < Nop
36 def execute(*keys)
37 IRB.JobManager.kill(*keys)
38 end
39 end
40 end
41 end
42 # :startdoc:
+0
-244
vendor/irb/completion.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/completion.rb -
3 # $Release Version: 0.9$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ishitsuka.com)
6 # From Original Idea of shugo@ruby-lang.org
7 #
8
9 require "readline"
10
11 module IRB
12 module InputCompletor # :nodoc:
13
14
15 # Set of reserved words used by Ruby, you should not use these for
16 # constants or variables
17 ReservedWords = %w[
18 BEGIN END
19 alias and
20 begin break
21 case class
22 def defined do
23 else elsif end ensure
24 false for
25 if in
26 module
27 next nil not
28 or
29 redo rescue retry return
30 self super
31 then true
32 undef unless until
33 when while
34 yield
35 ]
36
37 CompletionProc = proc { |input|
38 bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
39
40 case input
41 when /^((["'`]).*\2)\.([^.]*)$/
42 # String
43 receiver = $1
44 message = Regexp.quote($3)
45
46 candidates = String.instance_methods.collect{|m| m.to_s}
47 select_message(receiver, message, candidates)
48
49 when /^(\/[^\/]*\/)\.([^.]*)$/
50 # Regexp
51 receiver = $1
52 message = Regexp.quote($2)
53
54 candidates = Regexp.instance_methods.collect{|m| m.to_s}
55 select_message(receiver, message, candidates)
56
57 when /^([^\]]*\])\.([^.]*)$/
58 # Array
59 receiver = $1
60 message = Regexp.quote($2)
61
62 candidates = Array.instance_methods.collect{|m| m.to_s}
63 select_message(receiver, message, candidates)
64
65 when /^([^\}]*\})\.([^.]*)$/
66 # Proc or Hash
67 receiver = $1
68 message = Regexp.quote($2)
69
70 candidates = Proc.instance_methods.collect{|m| m.to_s}
71 candidates |= Hash.instance_methods.collect{|m| m.to_s}
72 select_message(receiver, message, candidates)
73
74 when /^(:[^:.]*)$/
75 # Symbol
76 if Symbol.respond_to?(:all_symbols)
77 sym = $1
78 candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name}
79 candidates.grep(/^#{Regexp.quote(sym)}/)
80 else
81 []
82 end
83
84 when /^::([A-Z][^:\.\(]*)$/
85 # Absolute Constant or class methods
86 receiver = $1
87 candidates = Object.constants.collect{|m| m.to_s}
88 candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
89
90 when /^([A-Z].*)::([^:.]*)$/
91 # Constant or class methods
92 receiver = $1
93 message = Regexp.quote($2)
94 begin
95 candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind)
96 candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind)
97 rescue Exception
98 candidates = []
99 end
100 select_message(receiver, message, candidates, "::")
101
102 when /^(:[^:.]+)(\.|::)([^.]*)$/
103 # Symbol
104 receiver = $1
105 sep = $2
106 message = Regexp.quote($3)
107
108 candidates = Symbol.instance_methods.collect{|m| m.to_s}
109 select_message(receiver, message, candidates, sep)
110
111 when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)(\.|::)([^.]*)$/
112 # Numeric
113 receiver = $1
114 sep = $5
115 message = Regexp.quote($6)
116
117 begin
118 candidates = eval(receiver, bind).methods.collect{|m| m.to_s}
119 rescue Exception
120 candidates = []
121 end
122 select_message(receiver, message, candidates, sep)
123
124 when /^(-?0x[0-9a-fA-F_]+)(\.|::)([^.]*)$/
125 # Numeric(0xFFFF)
126 receiver = $1
127 sep = $2
128 message = Regexp.quote($3)
129
130 begin
131 candidates = eval(receiver, bind).methods.collect{|m| m.to_s}
132 rescue Exception
133 candidates = []
134 end
135 select_message(receiver, message, candidates, sep)
136
137 when /^(\$[^.]*)$/
138 # global var
139 regmessage = Regexp.new(Regexp.quote($1))
140 candidates = global_variables.collect{|m| m.to_s}.grep(regmessage)
141
142 when /^([^."].*)(\.|::)([^.]*)$/
143 # variable.func or func.func
144 receiver = $1
145 sep = $2
146 message = Regexp.quote($3)
147
148 gv = eval("global_variables", bind).collect{|m| m.to_s}
149 lv = eval("local_variables", bind).collect{|m| m.to_s}
150 iv = eval("instance_variables", bind).collect{|m| m.to_s}
151 cv = eval("self.class.constants", bind).collect{|m| m.to_s}
152
153 if (gv | lv | iv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver
154 # foo.func and foo is var. OR
155 # foo::func and foo is var. OR
156 # foo::Const and foo is var. OR
157 # Foo::Bar.func
158 begin
159 candidates = []
160 rec = eval(receiver, bind)
161 if sep == "::" and rec.kind_of?(Module)
162 candidates = rec.constants.collect{|m| m.to_s}
163 end
164 candidates |= rec.methods.collect{|m| m.to_s}
165 rescue Exception
166 candidates = []
167 end
168 else
169 # func1.func2
170 candidates = []
171 to_ignore = ignored_modules
172 ObjectSpace.each_object(Module){|m|
173 next if (to_ignore.include?(m) rescue true)
174 candidates.concat m.instance_methods(false).collect{|x| x.to_s}
175 }
176 candidates.sort!
177 candidates.uniq!
178 end
179 select_message(receiver, message, candidates, sep)
180
181 when /^\.([^.]*)$/
182 # unknown(maybe String)
183
184 receiver = ""
185 message = Regexp.quote($1)
186
187 candidates = String.instance_methods(true).collect{|m| m.to_s}
188 select_message(receiver, message, candidates)
189
190 else
191 candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s}
192
193 (candidates|ReservedWords).grep(/^#{Regexp.quote(input)}/)
194 end
195 }
196
197 # Set of available operators in Ruby
198 Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~]
199
200 def self.select_message(receiver, message, candidates, sep = ".")
201 candidates.grep(/^#{message}/).collect do |e|
202 case e
203 when /^[a-zA-Z_]/
204 receiver + sep + e
205 when /^[0-9]/
206 when *Operators
207 #receiver + " " + e
208 end
209 end
210 end
211
212 def self.ignored_modules
213 # We could cache the result, but this is very fast already.
214 # By using this approach, we avoid Module#name calls, which are
215 # relatively slow when there are a lot of anonymous modules defined.
216 s = {}
217
218 scanner = lambda do |m|
219 next if s.include?(m) # IRB::ExtendCommandBundle::EXCB recurses.
220 s[m] = true
221 m.constants(false).each do |c|
222 value = m.const_get(c)
223 scanner.call(value) if value.is_a?(Module)
224 end
225 end
226
227 %i(IRB SLex RubyLex RubyToken).each do |sym|
228 next unless Object.const_defined?(sym)
229 scanner.call(Object.const_get(sym))
230 end
231
232 s.delete(IRB::Context) if defined?(IRB::Context)
233
234 s
235 end
236 end
237 end
238
239 if Readline.respond_to?("basic_word_break_characters=")
240 Readline.basic_word_break_characters= " \t\n`><=;|&{("
241 end
242 Readline.completion_append_character = nil
243 Readline.completion_proc = IRB::InputCompletor::CompletionProc
+0
-425
vendor/irb/context.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/context.rb - irb context
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 require_relative "workspace"
12 require_relative "inspector"
13 require_relative "input-method"
14 require_relative "output-method"
15
16 module IRB
17 # A class that wraps the current state of the irb session, including the
18 # configuration of IRB.conf.
19 class Context
20 # Creates a new IRB context.
21 #
22 # The optional +input_method+ argument:
23 #
24 # +nil+:: uses stdin or Readline
25 # +String+:: uses a File
26 # +other+:: uses this as InputMethod
27 def initialize(irb, workspace = nil, input_method = nil, output_method = nil)
28 @irb = irb
29 if workspace
30 @workspace = workspace
31 else
32 @workspace = WorkSpace.new
33 end
34 @thread = Thread.current if defined? Thread
35
36 # copy of default configuration
37 @ap_name = IRB.conf[:AP_NAME]
38 @rc = IRB.conf[:RC]
39 @load_modules = IRB.conf[:LOAD_MODULES]
40
41 @use_readline = IRB.conf[:USE_READLINE]
42 @verbose = IRB.conf[:VERBOSE]
43 @io = nil
44
45 self.inspect_mode = IRB.conf[:INSPECT_MODE]
46 self.use_tracer = IRB.conf[:USE_TRACER] if IRB.conf[:USE_TRACER]
47 self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER]
48 self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY]
49
50 @ignore_sigint = IRB.conf[:IGNORE_SIGINT]
51 @ignore_eof = IRB.conf[:IGNORE_EOF]
52
53 @back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT]
54
55 self.prompt_mode = IRB.conf[:PROMPT_MODE]
56
57 if IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager)
58 @irb_name = IRB.conf[:IRB_NAME]
59 else
60 @irb_name = IRB.conf[:IRB_NAME]+"#"+IRB.JobManager.n_jobs.to_s
61 end
62 @irb_path = "(" + @irb_name + ")"
63
64 case input_method
65 when nil
66 case use_readline?
67 when nil
68 if (defined?(ReadlineInputMethod) && STDIN.tty? &&
69 IRB.conf[:PROMPT_MODE] != :INF_RUBY)
70 @io = ReadlineInputMethod.new
71 else
72 @io = StdioInputMethod.new
73 end
74 when false
75 @io = StdioInputMethod.new
76 when true
77 if defined?(ReadlineInputMethod)
78 @io = ReadlineInputMethod.new
79 else
80 @io = StdioInputMethod.new
81 end
82 end
83
84 when String
85 @io = FileInputMethod.new(input_method)
86 @irb_name = File.basename(input_method)
87 @irb_path = input_method
88 else
89 @io = input_method
90 end
91 self.save_history = IRB.conf[:SAVE_HISTORY] if IRB.conf[:SAVE_HISTORY]
92
93 if output_method
94 @output_method = output_method
95 else
96 @output_method = StdioOutputMethod.new
97 end
98
99 @echo = IRB.conf[:ECHO]
100 if @echo.nil?
101 @echo = true
102 end
103 self.debug_level = IRB.conf[:DEBUG_LEVEL]
104 end
105
106 # The top-level workspace, see WorkSpace#main
107 def main
108 @workspace.main
109 end
110
111 # The toplevel workspace, see #home_workspace
112 attr_reader :workspace_home
113 # WorkSpace in the current context
114 attr_accessor :workspace
115 # The current thread in this context
116 attr_reader :thread
117 # The current input method
118 #
119 # Can be either StdioInputMethod, ReadlineInputMethod, FileInputMethod or
120 # other specified when the context is created. See ::new for more
121 # information on +input_method+.
122 attr_accessor :io
123
124 # Current irb session
125 attr_accessor :irb
126 # A copy of the default <code>IRB.conf[:AP_NAME]</code>
127 attr_accessor :ap_name
128 # A copy of the default <code>IRB.conf[:RC]</code>
129 attr_accessor :rc
130 # A copy of the default <code>IRB.conf[:LOAD_MODULES]</code>
131 attr_accessor :load_modules
132 # Can be either name from <code>IRB.conf[:IRB_NAME]</code>, or the number of
133 # the current job set by JobManager, such as <code>irb#2</code>
134 attr_accessor :irb_name
135 # Can be either the #irb_name surrounded by parenthesis, or the
136 # +input_method+ passed to Context.new
137 attr_accessor :irb_path
138
139 # Whether +Readline+ is enabled or not.
140 #
141 # A copy of the default <code>IRB.conf[:USE_READLINE]</code>
142 #
143 # See #use_readline= for more information.
144 attr_reader :use_readline
145 # A copy of the default <code>IRB.conf[:INSPECT_MODE]</code>
146 attr_reader :inspect_mode
147
148 # A copy of the default <code>IRB.conf[:PROMPT_MODE]</code>
149 attr_reader :prompt_mode
150 # Standard IRB prompt
151 #
152 # See IRB@Customizing+the+IRB+Prompt for more information.
153 attr_accessor :prompt_i
154 # IRB prompt for continuated strings
155 #
156 # See IRB@Customizing+the+IRB+Prompt for more information.
157 attr_accessor :prompt_s
158 # IRB prompt for continuated statement (e.g. immediately after an +if+)
159 #
160 # See IRB@Customizing+the+IRB+Prompt for more information.
161 attr_accessor :prompt_c
162 # See IRB@Customizing+the+IRB+Prompt for more information.
163 attr_accessor :prompt_n
164 # Can be either the default <code>IRB.conf[:AUTO_INDENT]</code>, or the
165 # mode set by #prompt_mode=
166 #
167 # To enable auto-indentation in irb:
168 #
169 # IRB.conf[:AUTO_INDENT] = true
170 #
171 # or
172 #
173 # irb_context.auto_indent_mode = true
174 #
175 # or
176 #
177 # IRB.CurrentContext.auto_indent_mode = true
178 #
179 # See IRB@Configuration for more information.
180 attr_accessor :auto_indent_mode
181 # The format of the return statement, set by #prompt_mode= using the
182 # +:RETURN+ of the +mode+ passed to set the current #prompt_mode.
183 attr_accessor :return_format
184
185 # Whether <code>^C</code> (+control-c+) will be ignored or not.
186 #
187 # If set to +false+, <code>^C</code> will quit irb.
188 #
189 # If set to +true+,
190 #
191 # * during input: cancel input then return to top level.
192 # * during execute: abandon current execution.
193 attr_accessor :ignore_sigint
194 # Whether <code>^D</code> (+control-d+) will be ignored or not.
195 #
196 # If set to +false+, <code>^D</code> will quit irb.
197 attr_accessor :ignore_eof
198 # Whether to echo the return value to output or not.
199 #
200 # Uses IRB.conf[:ECHO] if available, or defaults to +true+.
201 #
202 # puts "hello"
203 # # hello
204 # #=> nil
205 # IRB.CurrentContext.echo = false
206 # puts "omg"
207 # # omg
208 attr_accessor :echo
209 # Whether verbose messages are displayed or not.
210 #
211 # A copy of the default <code>IRB.conf[:VERBOSE]</code>
212 attr_accessor :verbose
213 # The debug level of irb
214 #
215 # See #debug_level= for more information.
216 attr_reader :debug_level
217
218 # The limit of backtrace lines displayed as top +n+ and tail +n+.
219 #
220 # The default value is 16.
221 #
222 # Can also be set using the +--back-trace-limit+ command line option.
223 #
224 # See IRB@Command+line+options for more command line options.
225 attr_accessor :back_trace_limit
226
227 # Alias for #use_readline
228 alias use_readline? use_readline
229 # Alias for #rc
230 alias rc? rc
231 alias ignore_sigint? ignore_sigint
232 alias ignore_eof? ignore_eof
233 alias echo? echo
234
235 # Returns whether messages are displayed or not.
236 def verbose?
237 if @verbose.nil?
238 if defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)
239 false
240 elsif !STDIN.tty? or @io.kind_of?(FileInputMethod)
241 true
242 else
243 false
244 end
245 else
246 @verbose
247 end
248 end
249
250 # Whether #verbose? is +true+, and +input_method+ is either
251 # StdioInputMethod or ReadlineInputMethod, see #io for more information.
252 def prompting?
253 verbose? || (STDIN.tty? && @io.kind_of?(StdioInputMethod) ||
254 (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)))
255 end
256
257 # The return value of the last statement evaluated.
258 attr_reader :last_value
259
260 # Sets the return value from the last statement evaluated in this context
261 # to #last_value.
262 def set_last_value(value)
263 @last_value = value
264 @workspace.local_variable_set :_, value
265 end
266
267 # Sets the +mode+ of the prompt in this context.
268 #
269 # See IRB@Customizing+the+IRB+Prompt for more information.
270 def prompt_mode=(mode)
271 @prompt_mode = mode
272 pconf = IRB.conf[:PROMPT][mode]
273 @prompt_i = pconf[:PROMPT_I]
274 @prompt_s = pconf[:PROMPT_S]
275 @prompt_c = pconf[:PROMPT_C]
276 @prompt_n = pconf[:PROMPT_N]
277 @return_format = pconf[:RETURN]
278 if ai = pconf.include?(:AUTO_INDENT)
279 @auto_indent_mode = ai
280 else
281 @auto_indent_mode = IRB.conf[:AUTO_INDENT]
282 end
283 end
284
285 # Whether #inspect_mode is set or not, see #inspect_mode= for more detail.
286 def inspect?
287 @inspect_mode.nil? or @inspect_mode
288 end
289
290 # Whether #io uses a File for the +input_method+ passed when creating the
291 # current context, see ::new
292 def file_input?
293 @io.class == FileInputMethod
294 end
295
296 # Specifies the inspect mode with +opt+:
297 #
298 # +true+:: display +inspect+
299 # +false+:: display +to_s+
300 # +nil+:: inspect mode in non-math mode,
301 # non-inspect mode in math mode
302 #
303 # See IRB::Inspector for more information.
304 #
305 # Can also be set using the +--inspect+ and +--noinspect+ command line
306 # options.
307 #
308 # See IRB@Command+line+options for more command line options.
309 def inspect_mode=(opt)
310
311 if i = Inspector::INSPECTORS[opt]
312 @inspect_mode = opt
313 @inspect_method = i
314 i.init
315 else
316 case opt
317 when nil
318 if Inspector.keys_with_inspector(Inspector::INSPECTORS[true]).include?(@inspect_mode)
319 self.inspect_mode = false
320 elsif Inspector.keys_with_inspector(Inspector::INSPECTORS[false]).include?(@inspect_mode)
321 self.inspect_mode = true
322 else
323 puts "Can't switch inspect mode."
324 return
325 end
326 when /^\s*\{.*\}\s*$/
327 begin
328 inspector = eval "proc#{opt}"
329 rescue Exception
330 puts "Can't switch inspect mode(#{opt})."
331 return
332 end
333 self.inspect_mode = inspector
334 when Proc
335 self.inspect_mode = IRB::Inspector(opt)
336 when Inspector
337 prefix = "usr%d"
338 i = 1
339 while Inspector::INSPECTORS[format(prefix, i)]; i += 1; end
340 @inspect_mode = format(prefix, i)
341 @inspect_method = opt
342 Inspector.def_inspector(format(prefix, i), @inspect_method)
343 else
344 puts "Can't switch inspect mode(#{opt})."
345 return
346 end
347 end
348 print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose?
349 @inspect_mode
350 end
351
352 # Obsolete method.
353 #
354 # Can be set using the +--noreadline+ and +--readline+ command line
355 # options.
356 #
357 # See IRB@Command+line+options for more command line options.
358 def use_readline=(opt)
359 print "This method is obsolete."
360 print "Do nothing."
361 end
362
363 # Sets the debug level of irb
364 #
365 # Can also be set using the +--irb_debug+ command line option.
366 #
367 # See IRB@Command+line+options for more command line options.
368 def debug_level=(value)
369 @debug_level = value
370 RubyLex.debug_level = value
371 end
372
373 # Whether or not debug mode is enabled, see #debug_level=.
374 def debug?
375 @debug_level > 0
376 end
377
378 def evaluate(line, line_no, exception: nil) # :nodoc:
379 @line_no = line_no
380 if exception
381 line = "begin ::Kernel.raise _; rescue _.class; #{line}; end"
382 @workspace.local_variable_set(:_, exception)
383 end
384 set_last_value(@workspace.evaluate(self, line, irb_path, line_no))
385 end
386
387 def inspect_last_value # :nodoc:
388 @inspect_method.inspect_value(@last_value)
389 end
390
391 alias __exit__ exit
392 # Exits the current session, see IRB.irb_exit
393 def exit(ret = 0)
394 IRB.irb_exit(@irb, ret)
395 end
396
397 NOPRINTING_IVARS = ["@last_value"] # :nodoc:
398 NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc:
399 IDNAME_IVARS = ["@prompt_mode"] # :nodoc:
400
401 alias __inspect__ inspect
402 def inspect # :nodoc:
403 array = []
404 for ivar in instance_variables.sort{|e1, e2| e1 <=> e2}
405 ivar = ivar.to_s
406 name = ivar.sub(/^@(.*)$/, '\1')
407 val = instance_eval(ivar)
408 case ivar
409 when *NOPRINTING_IVARS
410 array.push format("conf.%s=%s", name, "...")
411 when *NO_INSPECTING_IVARS
412 array.push format("conf.%s=%s", name, val.to_s)
413 when *IDNAME_IVARS
414 array.push format("conf.%s=:%s", name, val.id2name)
415 else
416 array.push format("conf.%s=%s", name, val.inspect)
417 end
418 end
419 array.join("\n")
420 end
421 alias __to_s__ to_s
422 alias to_s inspect
423 end
424 end
+0
-46
vendor/irb/ext/change-ws.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/ext/cb.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 module IRB # :nodoc:
13 class Context
14
15 # Inherited from +TOPLEVEL_BINDING+.
16 def home_workspace
17 if defined? @home_workspace
18 @home_workspace
19 else
20 @home_workspace = @workspace
21 end
22 end
23
24 # Changes the current workspace to given object or binding.
25 #
26 # If the optional argument is omitted, the workspace will be
27 # #home_workspace which is inherited from +TOPLEVEL_BINDING+ or the main
28 # object, <code>IRB.conf[:MAIN_CONTEXT]</code> when irb was initialized.
29 #
30 # See IRB::WorkSpace.new for more information.
31 def change_workspace(*_main)
32 if _main.empty?
33 @workspace = home_workspace
34 return main
35 end
36
37 @workspace = WorkSpace.new(_main[0])
38
39 if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
40 main.extend ExtendCommandBundle
41 end
42 end
43 end
44 end
45
+0
-119
vendor/irb/ext/history.rb less more
0 # frozen_string_literal: false
1 #
2 # history.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 module IRB # :nodoc:
13
14 class Context
15
16 NOPRINTING_IVARS.push "@eval_history_values"
17
18 # See #set_last_value
19 alias _set_last_value set_last_value
20
21 def set_last_value(value)
22 _set_last_value(value)
23
24 if @eval_history
25 @eval_history_values.push @line_no, @last_value
26 @workspace.evaluate self, "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
27 end
28
29 @last_value
30 end
31
32 # The command result history limit.
33 attr_reader :eval_history
34 # Sets command result history limit.
35 #
36 # +no+ is an Integer or +nil+.
37 #
38 # Returns +no+ of history items if greater than 0.
39 #
40 # If +no+ is 0, the number of history items is unlimited.
41 #
42 # If +no+ is +nil+, execution result history isn't used (default).
43 def eval_history=(no)
44 if no
45 if defined?(@eval_history) && @eval_history
46 @eval_history_values.size(no)
47 else
48 @eval_history_values = History.new(no)
49 IRB.conf[:__TMP__EHV__] = @eval_history_values
50 @workspace.evaluate(self, "__ = IRB.conf[:__TMP__EHV__]")
51 IRB.conf.delete(:__TMP_EHV__)
52 end
53 else
54 @eval_history_values = nil
55 end
56 @eval_history = no
57 end
58 end
59
60 class History # :nodoc:
61
62 def initialize(size = 16)
63 @size = size
64 @contents = []
65 end
66
67 def size(size)
68 if size != 0 && size < @size
69 @contents = @contents[@size - size .. @size]
70 end
71 @size = size
72 end
73
74 def [](idx)
75 begin
76 if idx >= 0
77 @contents.find{|no, val| no == idx}[1]
78 else
79 @contents[idx][1]
80 end
81 rescue NameError
82 nil
83 end
84 end
85
86 def push(no, val)
87 @contents.push [no, val]
88 @contents.shift if @size != 0 && @contents.size > @size
89 end
90
91 alias real_inspect inspect
92
93 def inspect
94 if @contents.empty?
95 return real_inspect
96 end
97
98 unless (last = @contents.pop)[1].equal?(self)
99 @contents.push last
100 last = nil
101 end
102 str = @contents.collect{|no, val|
103 if val.equal?(self)
104 "#{no} ...self-history..."
105 else
106 "#{no} #{val.inspect}"
107 end
108 }.join("\n")
109 if str == ""
110 str = "Empty."
111 end
112 @contents.push last if last
113 str
114 end
115 end
116 end
117
118
+0
-129
vendor/irb/ext/loader.rb less more
0 # frozen_string_literal: false
1 #
2 # loader.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12
13 module IRB # :nodoc:
14 # Raised in the event of an exception in a file loaded from an Irb session
15 class LoadAbort < Exception;end
16
17 # Provides a few commands for loading files within an irb session.
18 #
19 # See ExtendCommandBundle for more information.
20 module IrbLoader
21
22 alias ruby_load load
23 alias ruby_require require
24
25 # Loads the given file similarly to Kernel#load
26 def irb_load(fn, priv = nil)
27 path = search_file_from_ruby_path(fn)
28 raise LoadError, "No such file to load -- #{fn}" unless path
29
30 load_file(path, priv)
31 end
32
33 def search_file_from_ruby_path(fn) # :nodoc:
34 if /^#{Regexp.quote(File::Separator)}/ =~ fn
35 return fn if File.exist?(fn)
36 return nil
37 end
38
39 for path in $:
40 if File.exist?(f = File.join(path, fn))
41 return f
42 end
43 end
44 return nil
45 end
46
47 # Loads a given file in the current session and displays the source lines
48 #
49 # See Irb#suspend_input_method for more information.
50 def source_file(path)
51 irb.suspend_name(path, File.basename(path)) do
52 irb.suspend_input_method(FileInputMethod.new(path)) do
53 |back_io|
54 irb.signal_status(:IN_LOAD) do
55 if back_io.kind_of?(FileInputMethod)
56 irb.eval_input
57 else
58 begin
59 irb.eval_input
60 rescue LoadAbort
61 print "load abort!!\n"
62 end
63 end
64 end
65 end
66 end
67 end
68
69 # Loads the given file in the current session's context and evaluates it.
70 #
71 # See Irb#suspend_input_method for more information.
72 def load_file(path, priv = nil)
73 irb.suspend_name(path, File.basename(path)) do
74
75 if priv
76 ws = WorkSpace.new(Module.new)
77 else
78 ws = WorkSpace.new
79 end
80 irb.suspend_workspace(ws) do
81 irb.suspend_input_method(FileInputMethod.new(path)) do
82 |back_io|
83 irb.signal_status(:IN_LOAD) do
84 if back_io.kind_of?(FileInputMethod)
85 irb.eval_input
86 else
87 begin
88 irb.eval_input
89 rescue LoadAbort
90 print "load abort!!\n"
91 end
92 end
93 end
94 end
95 end
96 end
97 end
98
99 def old # :nodoc:
100 back_io = @io
101 back_path = @irb_path
102 back_name = @irb_name
103 back_scanner = @irb.scanner
104 begin
105 @io = FileInputMethod.new(path)
106 @irb_name = File.basename(path)
107 @irb_path = path
108 @irb.signal_status(:IN_LOAD) do
109 if back_io.kind_of?(FileInputMethod)
110 @irb.eval_input
111 else
112 begin
113 @irb.eval_input
114 rescue LoadAbort
115 print "load abort!!\n"
116 end
117 end
118 end
119 ensure
120 @io = back_io
121 @irb_name = back_name
122 @irb_path = back_path
123 @irb.scanner = back_scanner
124 end
125 end
126 end
127 end
128
+0
-265
vendor/irb/ext/multi-irb.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/multi-irb.rb - multiple irb module
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 IRB.fail CantShiftToMultiIrbMode unless defined?(Thread)
12
13 module IRB
14 class JobManager
15
16 # Creates a new JobManager object
17 def initialize
18 @jobs = []
19 @current_job = nil
20 end
21
22 # The active irb session
23 attr_accessor :current_job
24
25 # The total number of irb sessions, used to set +irb_name+ of the current
26 # Context.
27 def n_jobs
28 @jobs.size
29 end
30
31 # Returns the thread for the given +key+ object, see #search for more
32 # information.
33 def thread(key)
34 th, = search(key)
35 th
36 end
37
38 # Returns the irb session for the given +key+ object, see #search for more
39 # information.
40 def irb(key)
41 _, irb = search(key)
42 irb
43 end
44
45 # Returns the top level thread.
46 def main_thread
47 @jobs[0][0]
48 end
49
50 # Returns the top level irb session.
51 def main_irb
52 @jobs[0][1]
53 end
54
55 # Add the given +irb+ session to the jobs Array.
56 def insert(irb)
57 @jobs.push [Thread.current, irb]
58 end
59
60 # Changes the current active irb session to the given +key+ in the jobs
61 # Array.
62 #
63 # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive.
64 #
65 # If the given irb session is already active, an IrbSwitchedToCurrentThread
66 # exception is raised.
67 def switch(key)
68 th, irb = search(key)
69 IRB.fail IrbAlreadyDead unless th.alive?
70 IRB.fail IrbSwitchedToCurrentThread if th == Thread.current
71 @current_job = irb
72 th.run
73 Thread.stop
74 @current_job = irb(Thread.current)
75 end
76
77 # Terminates the irb sessions specified by the given +keys+.
78 #
79 # Raises an IrbAlreadyDead exception if one of the given +keys+ is already
80 # terminated.
81 #
82 # See Thread#exit for more information.
83 def kill(*keys)
84 for key in keys
85 th, _ = search(key)
86 IRB.fail IrbAlreadyDead unless th.alive?
87 th.exit
88 end
89 end
90
91 # Returns the associated job for the given +key+.
92 #
93 # If given an Integer, it will return the +key+ index for the jobs Array.
94 #
95 # When an instance of Irb is given, it will return the irb session
96 # associated with +key+.
97 #
98 # If given an instance of Thread, it will return the associated thread
99 # +key+ using Object#=== on the jobs Array.
100 #
101 # Otherwise returns the irb session with the same top-level binding as the
102 # given +key+.
103 #
104 # Raises a NoSuchJob exception if no job can be found with the given +key+.
105 def search(key)
106 job = case key
107 when Integer
108 @jobs[key]
109 when Irb
110 @jobs.find{|k, v| v.equal?(key)}
111 when Thread
112 @jobs.assoc(key)
113 else
114 @jobs.find{|k, v| v.context.main.equal?(key)}
115 end
116 IRB.fail NoSuchJob, key if job.nil?
117 job
118 end
119
120 # Deletes the job at the given +key+.
121 def delete(key)
122 case key
123 when Integer
124 IRB.fail NoSuchJob, key unless @jobs[key]
125 @jobs[key] = nil
126 else
127 catch(:EXISTS) do
128 @jobs.each_index do
129 |i|
130 if @jobs[i] and (@jobs[i][0] == key ||
131 @jobs[i][1] == key ||
132 @jobs[i][1].context.main.equal?(key))
133 @jobs[i] = nil
134 throw :EXISTS
135 end
136 end
137 IRB.fail NoSuchJob, key
138 end
139 end
140 until assoc = @jobs.pop; end unless @jobs.empty?
141 @jobs.push assoc
142 end
143
144 # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+.
145 def inspect
146 ary = []
147 @jobs.each_index do
148 |i|
149 th, irb = @jobs[i]
150 next if th.nil?
151
152 if th.alive?
153 if th.stop?
154 t_status = "stop"
155 else
156 t_status = "running"
157 end
158 else
159 t_status = "exited"
160 end
161 ary.push format("#%d->%s on %s (%s: %s)",
162 i,
163 irb.context.irb_name,
164 irb.context.main,
165 th,
166 t_status)
167 end
168 ary.join("\n")
169 end
170 end
171
172 @JobManager = JobManager.new
173
174 # The current JobManager in the session
175 def IRB.JobManager
176 @JobManager
177 end
178
179 # The current Context in this session
180 def IRB.CurrentContext
181 IRB.JobManager.irb(Thread.current).context
182 end
183
184 # Creates a new IRB session, see Irb.new.
185 #
186 # The optional +file+ argument is given to Context.new, along with the
187 # workspace created with the remaining arguments, see WorkSpace.new
188 def IRB.irb(file = nil, *main)
189 workspace = WorkSpace.new(*main)
190 parent_thread = Thread.current
191 Thread.start do
192 begin
193 irb = Irb.new(workspace, file)
194 rescue
195 print "Subirb can't start with context(self): ", workspace.main.inspect, "\n"
196 print "return to main irb\n"
197 Thread.pass
198 Thread.main.wakeup
199 Thread.exit
200 end
201 @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
202 @JobManager.insert(irb)
203 @JobManager.current_job = irb
204 begin
205 system_exit = false
206 catch(:IRB_EXIT) do
207 irb.eval_input
208 end
209 rescue SystemExit
210 system_exit = true
211 raise
212 #fail
213 ensure
214 unless system_exit
215 @JobManager.delete(irb)
216 if @JobManager.current_job == irb
217 if parent_thread.alive?
218 @JobManager.current_job = @JobManager.irb(parent_thread)
219 parent_thread.run
220 else
221 @JobManager.current_job = @JobManager.main_irb
222 @JobManager.main_thread.run
223 end
224 end
225 end
226 end
227 end
228 Thread.stop
229 @JobManager.current_job = @JobManager.irb(Thread.current)
230 end
231
232 @CONF[:SINGLE_IRB_MODE] = false
233 @JobManager.insert(@CONF[:MAIN_CONTEXT].irb)
234 @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb
235
236 class Irb
237 def signal_handle
238 unless @context.ignore_sigint?
239 print "\nabort!!\n" if @context.verbose?
240 exit
241 end
242
243 case @signal_status
244 when :IN_INPUT
245 print "^C\n"
246 IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput
247 when :IN_EVAL
248 IRB.irb_abort(self)
249 when :IN_LOAD
250 IRB.irb_abort(self, LoadAbort)
251 when :IN_IRB
252 # ignore
253 else
254 # ignore other cases as well
255 end
256 end
257 end
258
259 trap("SIGINT") do
260 @JobManager.current_job.signal_handle
261 Thread.stop
262 end
263
264 end
+0
-105
vendor/irb/ext/save-history.rb less more
0 # frozen_string_literal: false
1 # save-history.rb -
2 # $Release Version: 0.9.6$
3 # $Revision$
4 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
5 #
6 # --
7 #
8 #
9 #
10
11 require "readline"
12
13 module IRB
14 module HistorySavingAbility # :nodoc:
15 end
16
17 class Context
18 def init_save_history# :nodoc:
19 unless (class<<@io;self;end).include?(HistorySavingAbility)
20 @io.extend(HistorySavingAbility)
21 end
22 end
23
24 # A copy of the default <code>IRB.conf[:SAVE_HISTORY]</code>
25 def save_history
26 IRB.conf[:SAVE_HISTORY]
27 end
28
29 remove_method :save_history= if respond_to?(:save_history=)
30 # Sets <code>IRB.conf[:SAVE_HISTORY]</code> to the given +val+ and calls
31 # #init_save_history with this context.
32 #
33 # Will store the number of +val+ entries of history in the #history_file
34 #
35 # Add the following to your +.irbrc+ to change the number of history
36 # entries stored to 1000:
37 #
38 # IRB.conf[:SAVE_HISTORY] = 1000
39 def save_history=(val)
40 IRB.conf[:SAVE_HISTORY] = val
41 if val
42 main_context = IRB.conf[:MAIN_CONTEXT]
43 main_context = self unless main_context
44 main_context.init_save_history
45 end
46 end
47
48 # A copy of the default <code>IRB.conf[:HISTORY_FILE]</code>
49 def history_file
50 IRB.conf[:HISTORY_FILE]
51 end
52
53 # Set <code>IRB.conf[:HISTORY_FILE]</code> to the given +hist+.
54 def history_file=(hist)
55 IRB.conf[:HISTORY_FILE] = hist
56 end
57 end
58
59 module HistorySavingAbility # :nodoc:
60 include Readline
61
62 def HistorySavingAbility.extended(obj)
63 IRB.conf[:AT_EXIT].push proc{obj.save_history}
64 obj.load_history
65 obj
66 end
67
68 def load_history
69 if history_file = IRB.conf[:HISTORY_FILE]
70 history_file = File.expand_path(history_file)
71 end
72 history_file = IRB.rc_file("_history") unless history_file
73 if File.exist?(history_file)
74 open(history_file) do |f|
75 f.each {|l| HISTORY << l.chomp}
76 end
77 end
78 end
79
80 def save_history
81 if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) > 0
82 if history_file = IRB.conf[:HISTORY_FILE]
83 history_file = File.expand_path(history_file)
84 end
85 history_file = IRB.rc_file("_history") unless history_file
86
87 # Change the permission of a file that already exists[BUG #7694]
88 begin
89 if File.stat(history_file).mode & 066 != 0
90 File.chmod(0600, history_file)
91 end
92 rescue Errno::ENOENT
93 rescue
94 raise
95 end
96
97 open(history_file, 'w', 0600 ) do |f|
98 hist = HISTORY.to_a
99 f.puts(hist[-num..-1] || hist)
100 end
101 end
102 end
103 end
104 end
+0
-72
vendor/irb/ext/tracer.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/lib/tracer.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 require "tracer"
12
13 module IRB
14
15 # initialize tracing function
16 def IRB.initialize_tracer
17 Tracer.verbose = false
18 Tracer.add_filter {
19 |event, file, line, id, binding, *rests|
20 /^#{Regexp.quote(@CONF[:IRB_LIB_PATH])}/ !~ file and
21 File::basename(file) != "irb.rb"
22 }
23 end
24
25 class Context
26 # Whether Tracer is used when evaluating statements in this context.
27 #
28 # See +lib/tracer.rb+ for more information.
29 attr_reader :use_tracer
30 alias use_tracer? use_tracer
31
32 # Sets whether or not to use the Tracer library when evaluating statements
33 # in this context.
34 #
35 # See +lib/tracer.rb+ for more information.
36 def use_tracer=(opt)
37 if opt
38 Tracer.set_get_line_procs(@irb_path) {
39 |line_no, *rests|
40 @io.line(line_no)
41 }
42 elsif !opt && @use_tracer
43 Tracer.off
44 end
45 @use_tracer=opt
46 end
47 end
48
49 class WorkSpace
50 alias __evaluate__ evaluate
51 # Evaluate the context of this workspace and use the Tracer library to
52 # output the exact lines of code are being executed in chronological order.
53 #
54 # See +lib/tracer.rb+ for more information.
55 def evaluate(context, statements, file = nil, line = nil)
56 if context.use_tracer? && file != nil && line != nil
57 Tracer.on
58 begin
59 __evaluate__(context, statements, file, line)
60 ensure
61 Tracer.off
62 end
63 else
64 __evaluate__(context, statements, file || __FILE__, line || __LINE__)
65 end
66 end
67 end
68
69 IRB.initialize_tracer
70 end
71
+0
-74
vendor/irb/ext/use-loader.rb less more
0 # frozen_string_literal: false
1 #
2 # use-loader.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require_relative "../cmd/load"
13 require_relative "loader"
14
15 class Object
16 alias __original__load__IRB_use_loader__ load
17 alias __original__require__IRB_use_loader__ require
18 end
19
20 module IRB
21 module ExtendCommandBundle
22 # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load
23 def irb_load(*opts, &b)
24 ExtendCommand::Load.execute(irb_context, *opts, &b)
25 end
26 # Loads the given file similarly to Kernel#require
27 def irb_require(*opts, &b)
28 ExtendCommand::Require.execute(irb_context, *opts, &b)
29 end
30 end
31
32 class Context
33
34 IRB.conf[:USE_LOADER] = false
35
36 # Returns whether +irb+'s own file reader method is used by
37 # +load+/+require+ or not.
38 #
39 # This mode is globally affected (irb-wide).
40 def use_loader
41 IRB.conf[:USE_LOADER]
42 end
43
44 alias use_loader? use_loader
45
46 # Sets IRB.conf[:USE_LOADER]
47 #
48 # See #use_loader for more information.
49 def use_loader=(opt)
50
51 if IRB.conf[:USE_LOADER] != opt
52 IRB.conf[:USE_LOADER] = opt
53 if opt
54 if !$".include?("irb/cmd/load")
55 end
56 (class<<@workspace.main;self;end).instance_eval {
57 alias_method :load, :irb_load
58 alias_method :require, :irb_require
59 }
60 else
61 (class<<@workspace.main;self;end).instance_eval {
62 alias_method :load, :__original__load__IRB_use_loader__
63 alias_method :require, :__original__require__IRB_use_loader__
64 }
65 end
66 end
67 print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose?
68 opt
69 end
70 end
71 end
72
73
+0
-67
vendor/irb/ext/workspaces.rb less more
0 # frozen_string_literal: false
1 #
2 # push-ws.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 module IRB # :nodoc:
13 class Context
14
15 # Size of the current WorkSpace stack
16 def irb_level
17 workspace_stack.size
18 end
19
20 # WorkSpaces in the current stack
21 def workspaces
22 if defined? @workspaces
23 @workspaces
24 else
25 @workspaces = []
26 end
27 end
28
29 # Creates a new workspace with the given object or binding, and appends it
30 # onto the current #workspaces stack.
31 #
32 # See IRB::Context#change_workspace and IRB::WorkSpace.new for more
33 # information.
34 def push_workspace(*_main)
35 if _main.empty?
36 if workspaces.empty?
37 print "No other workspace\n"
38 return nil
39 end
40 ws = workspaces.pop
41 workspaces.push @workspace
42 @workspace = ws
43 return workspaces
44 end
45
46 workspaces.push @workspace
47 @workspace = WorkSpace.new(@workspace.binding, _main[0])
48 if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
49 main.extend ExtendCommandBundle
50 end
51 end
52
53 # Removes the last element from the current #workspaces stack and returns
54 # it, or +nil+ if the current workspace stack is empty.
55 #
56 # Also, see #push_workspace.
57 def pop_workspace
58 if workspaces.empty?
59 print "workspace stack empty\n"
60 return
61 end
62 @workspace = workspaces.pop
63 end
64 end
65 end
66
+0
-306
vendor/irb/extend-command.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/extend-command.rb - irb extend command
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 module IRB # :nodoc:
12 # Installs the default irb extensions command bundle.
13 module ExtendCommandBundle
14 EXCB = ExtendCommandBundle # :nodoc:
15
16 # See #install_alias_method.
17 NO_OVERRIDE = 0
18 # See #install_alias_method.
19 OVERRIDE_PRIVATE_ONLY = 0x01
20 # See #install_alias_method.
21 OVERRIDE_ALL = 0x02
22
23 # Quits the current irb context
24 #
25 # +ret+ is the optional signal or message to send to Context#exit
26 #
27 # Same as <code>IRB.CurrentContext.exit</code>.
28 def irb_exit(ret = 0)
29 irb_context.exit(ret)
30 end
31
32 # Displays current configuration.
33 #
34 # Modifing the configuration is achieved by sending a message to IRB.conf.
35 def irb_context
36 IRB.CurrentContext
37 end
38
39 @ALIASES = [
40 [:context, :irb_context, NO_OVERRIDE],
41 [:conf, :irb_context, NO_OVERRIDE],
42 [:irb_quit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
43 [:exit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
44 [:quit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
45 ]
46
47 @EXTEND_COMMANDS = [
48 [:irb_current_working_workspace, :CurrentWorkingWorkspace, "irb/cmd/chws",
49 [:irb_print_working_workspace, OVERRIDE_ALL],
50 [:irb_cwws, OVERRIDE_ALL],
51 [:irb_pwws, OVERRIDE_ALL],
52 [:cwws, NO_OVERRIDE],
53 [:pwws, NO_OVERRIDE],
54 [:irb_current_working_binding, OVERRIDE_ALL],
55 [:irb_print_working_binding, OVERRIDE_ALL],
56 [:irb_cwb, OVERRIDE_ALL],
57 [:irb_pwb, OVERRIDE_ALL],
58 ],
59 [:irb_change_workspace, :ChangeWorkspace, "irb/cmd/chws",
60 [:irb_chws, OVERRIDE_ALL],
61 [:irb_cws, OVERRIDE_ALL],
62 [:chws, NO_OVERRIDE],
63 [:cws, NO_OVERRIDE],
64 [:irb_change_binding, OVERRIDE_ALL],
65 [:irb_cb, OVERRIDE_ALL],
66 [:cb, NO_OVERRIDE]],
67
68 [:irb_workspaces, :Workspaces, "irb/cmd/pushws",
69 [:workspaces, NO_OVERRIDE],
70 [:irb_bindings, OVERRIDE_ALL],
71 [:bindings, NO_OVERRIDE]],
72 [:irb_push_workspace, :PushWorkspace, "irb/cmd/pushws",
73 [:irb_pushws, OVERRIDE_ALL],
74 [:pushws, NO_OVERRIDE],
75 [:irb_push_binding, OVERRIDE_ALL],
76 [:irb_pushb, OVERRIDE_ALL],
77 [:pushb, NO_OVERRIDE]],
78 [:irb_pop_workspace, :PopWorkspace, "irb/cmd/pushws",
79 [:irb_popws, OVERRIDE_ALL],
80 [:popws, NO_OVERRIDE],
81 [:irb_pop_binding, OVERRIDE_ALL],
82 [:irb_popb, OVERRIDE_ALL],
83 [:popb, NO_OVERRIDE]],
84
85 [:irb_load, :Load, "irb/cmd/load"],
86 [:irb_require, :Require, "irb/cmd/load"],
87 [:irb_source, :Source, "irb/cmd/load",
88 [:source, NO_OVERRIDE]],
89
90 [:irb, :IrbCommand, "irb/cmd/subirb"],
91 [:irb_jobs, :Jobs, "irb/cmd/subirb",
92 [:jobs, NO_OVERRIDE]],
93 [:irb_fg, :Foreground, "irb/cmd/subirb",
94 [:fg, NO_OVERRIDE]],
95 [:irb_kill, :Kill, "irb/cmd/subirb",
96 [:kill, OVERRIDE_PRIVATE_ONLY]],
97
98 [:irb_help, :Help, "irb/cmd/help",
99 [:help, NO_OVERRIDE]],
100
101 ]
102
103 # Installs the default irb commands:
104 #
105 # +irb_current_working_workspace+:: Context#main
106 # +irb_change_workspace+:: Context#change_workspace
107 # +irb_workspaces+:: Context#workspaces
108 # +irb_push_workspace+:: Context#push_workspace
109 # +irb_pop_workspace+:: Context#pop_workspace
110 # +irb_load+:: #irb_load
111 # +irb_require+:: #irb_require
112 # +irb_source+:: IrbLoader#source_file
113 # +irb+:: IRB.irb
114 # +irb_jobs+:: JobManager
115 # +irb_fg+:: JobManager#switch
116 # +irb_kill+:: JobManager#kill
117 # +irb_help+:: IRB@Command+line+options
118 def self.install_extend_commands
119 for args in @EXTEND_COMMANDS
120 def_extend_command(*args)
121 end
122 end
123
124 # Evaluate the given +cmd_name+ on the given +cmd_class+ Class.
125 #
126 # Will also define any given +aliases+ for the method.
127 #
128 # The optional +load_file+ parameter will be required within the method
129 # definition.
130 def self.def_extend_command(cmd_name, cmd_class, load_file = nil, *aliases)
131 case cmd_class
132 when Symbol
133 cmd_class = cmd_class.id2name
134 when String
135 when Class
136 cmd_class = cmd_class.name
137 end
138
139 if load_file
140 line = __LINE__; eval %[
141 def #{cmd_name}(*opts, &b)
142 require "#{load_file}"
143 arity = ExtendCommand::#{cmd_class}.instance_method(:execute).arity
144 args = (1..(arity < 0 ? ~arity : arity)).map {|i| "arg" + i.to_s }
145 args << "*opts" if arity < 0
146 args << "&block"
147 args = args.join(", ")
148 line = __LINE__; eval %[
149 def #{cmd_name}(\#{args})
150 ExtendCommand::#{cmd_class}.execute(irb_context, \#{args})
151 end
152 ], nil, __FILE__, line
153 send :#{cmd_name}, *opts, &b
154 end
155 ], nil, __FILE__, line
156 else
157 line = __LINE__; eval %[
158 def #{cmd_name}(*opts, &b)
159 ExtendCommand::#{cmd_class}.execute(irb_context, *opts, &b)
160 end
161 ], nil, __FILE__, line
162 end
163
164 for ali, flag in aliases
165 @ALIASES.push [ali, cmd_name, flag]
166 end
167 end
168
169 # Installs alias methods for the default irb commands, see
170 # ::install_extend_commands.
171 def install_alias_method(to, from, override = NO_OVERRIDE)
172 to = to.id2name unless to.kind_of?(String)
173 from = from.id2name unless from.kind_of?(String)
174
175 if override == OVERRIDE_ALL or
176 (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or
177 (override == NO_OVERRIDE) && !respond_to?(to, true)
178 target = self
179 (class << self; self; end).instance_eval{
180 if target.respond_to?(to, true) &&
181 !target.respond_to?(EXCB.irb_original_method_name(to), true)
182 alias_method(EXCB.irb_original_method_name(to), to)
183 end
184 alias_method to, from
185 }
186 else
187 print "irb: warn: can't alias #{to} from #{from}.\n"
188 end
189 end
190
191 def self.irb_original_method_name(method_name) # :nodoc:
192 "irb_" + method_name + "_org"
193 end
194
195 # Installs alias methods for the default irb commands on the given object
196 # using #install_alias_method.
197 def self.extend_object(obj)
198 unless (class << obj; ancestors; end).include?(EXCB)
199 super
200 for ali, com, flg in @ALIASES
201 obj.install_alias_method(ali, com, flg)
202 end
203 end
204 end
205
206 install_extend_commands
207 end
208
209 # Extends methods for the Context module
210 module ContextExtender
211 CE = ContextExtender # :nodoc:
212
213 @EXTEND_COMMANDS = [
214 [:eval_history=, "irb/ext/history.rb"],
215 [:use_tracer=, "irb/ext/tracer.rb"],
216 [:use_loader=, "irb/ext/use-loader.rb"],
217 [:save_history=, "irb/ext/save-history.rb"],
218 ]
219
220 # Installs the default context extensions as irb commands:
221 #
222 # Context#eval_history=:: +irb/ext/history.rb+
223 # Context#use_tracer=:: +irb/ext/tracer.rb+
224 # Context#use_loader=:: +irb/ext/use-loader.rb+
225 # Context#save_history=:: +irb/ext/save-history.rb+
226 def self.install_extend_commands
227 for args in @EXTEND_COMMANDS
228 def_extend_command(*args)
229 end
230 end
231
232 # Evaluate the given +command+ from the given +load_file+ on the Context
233 # module.
234 #
235 # Will also define any given +aliases+ for the method.
236 def self.def_extend_command(cmd_name, load_file, *aliases)
237 line = __LINE__; Context.module_eval %[
238 def #{cmd_name}(*opts, &b)
239 Context.module_eval {remove_method(:#{cmd_name})}
240 require "#{load_file}"
241 send :#{cmd_name}, *opts, &b
242 end
243 for ali in aliases
244 alias_method ali, cmd_name
245 end
246 ], __FILE__, line
247 end
248
249 CE.install_extend_commands
250 end
251
252 # A convenience module for extending Ruby methods.
253 module MethodExtender
254 # Extends the given +base_method+ with a prefix call to the given
255 # +extend_method+.
256 def def_pre_proc(base_method, extend_method)
257 base_method = base_method.to_s
258 extend_method = extend_method.to_s
259
260 alias_name = new_alias_name(base_method)
261 module_eval %[
262 alias_method alias_name, base_method
263 def #{base_method}(*opts)
264 send :#{extend_method}, *opts
265 send :#{alias_name}, *opts
266 end
267 ]
268 end
269
270 # Extends the given +base_method+ with a postfix call to the given
271 # +extend_method+.
272 def def_post_proc(base_method, extend_method)
273 base_method = base_method.to_s
274 extend_method = extend_method.to_s
275
276 alias_name = new_alias_name(base_method)
277 module_eval %[
278 alias_method alias_name, base_method
279 def #{base_method}(*opts)
280 send :#{alias_name}, *opts
281 send :#{extend_method}, *opts
282 end
283 ]
284 end
285
286 # Returns a unique method name to use as an alias for the given +name+.
287 #
288 # Usually returns <code>#{prefix}#{name}#{postfix}<num></code>, example:
289 #
290 # new_alias_name('foo') #=> __alias_of__foo__
291 # def bar; end
292 # new_alias_name('bar') #=> __alias_of__bar__2
293 def new_alias_name(name, prefix = "__alias_of__", postfix = "__")
294 base_name = "#{prefix}#{name}#{postfix}"
295 all_methods = instance_methods(true) + private_instance_methods(true)
296 same_methods = all_methods.grep(/^#{Regexp.quote(base_name)}[0-9]*$/)
297 return base_name if same_methods.empty?
298 no = same_methods.size
299 while !same_methods.include?(alias_name = base_name + no)
300 no += 1
301 end
302 alias_name
303 end
304 end
305 end
+0
-81
vendor/irb/frame.rb less more
0 # frozen_string_literal: false
1 #
2 # frame.rb -
3 # $Release Version: 0.9$
4 # $Revision$
5 # by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd)
6 #
7 # --
8 #
9 #
10 #
11
12 require "e2mmap"
13
14 module IRB
15 class Frame
16 extend Exception2MessageMapper
17 def_exception :FrameOverflow, "frame overflow"
18 def_exception :FrameUnderflow, "frame underflow"
19
20 # Default number of stack frames
21 INIT_STACK_TIMES = 3
22 # Default number of frames offset
23 CALL_STACK_OFFSET = 3
24
25 # Creates a new stack frame
26 def initialize
27 @frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES
28 end
29
30 # Used by Kernel#set_trace_func to register each event in the call stack
31 def trace_func(event, file, line, id, binding)
32 case event
33 when 'call', 'class'
34 @frames.push binding
35 when 'return', 'end'
36 @frames.pop
37 end
38 end
39
40 # Returns the +n+ number of frames on the call stack from the last frame
41 # initialized.
42 #
43 # Raises FrameUnderflow if there are no frames in the given stack range.
44 def top(n = 0)
45 bind = @frames[-(n + CALL_STACK_OFFSET)]
46 Fail FrameUnderflow unless bind
47 bind
48 end
49
50 # Returns the +n+ number of frames on the call stack from the first frame
51 # initialized.
52 #
53 # Raises FrameOverflow if there are no frames in the given stack range.
54 def bottom(n = 0)
55 bind = @frames[n]
56 Fail FrameOverflow unless bind
57 bind
58 end
59
60 # Convenience method for Frame#bottom
61 def Frame.bottom(n = 0)
62 @backtrace.bottom(n)
63 end
64
65 # Convenience method for Frame#top
66 def Frame.top(n = 0)
67 @backtrace.top(n)
68 end
69
70 # Returns the binding context of the caller from the last frame initialized
71 def Frame.sender
72 eval "self", @backtrace.top
73 end
74
75 @backtrace = Frame.new
76 set_trace_func proc{|event, file, line, id, binding, klass|
77 @backtrace.trace_func(event, file, line, id, binding)
78 }
79 end
80 end
+0
-37
vendor/irb/help.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/help.rb - print usage module
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ishitsuka.com)
6 #
7 # --
8 #
9 #
10 #
11
12 require_relative 'magic-file'
13
14 module IRB
15 # Outputs the irb help message, see IRB@Command+line+options.
16 def IRB.print_usage
17 lc = IRB.conf[:LC_MESSAGES]
18 path = lc.find("irb/help-message")
19 space_line = false
20 IRB::MagicFile.open(path){|f|
21 f.each_line do |l|
22 if /^\s*$/ =~ l
23 lc.puts l unless space_line
24 space_line = true
25 next
26 end
27 space_line = false
28
29 l.sub!(/#.*$/, "")
30 next if /^\s*$/ =~ l
31 lc.puts l
32 end
33 }
34 end
35 end
36
+0
-302
vendor/irb/init.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/init.rb - irb initialize module
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 module IRB # :nodoc:
13
14 # initialize config
15 def IRB.setup(ap_path, argv: ::ARGV)
16 IRB.init_config(ap_path)
17 IRB.init_error
18 IRB.parse_opts(argv: argv)
19 IRB.run_config
20 IRB.load_modules
21
22 unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]]
23 IRB.fail(UndefinedPromptMode, @CONF[:PROMPT_MODE])
24 end
25 end
26
27 # @CONF default setting
28 def IRB.init_config(ap_path)
29 # class instance variables
30 @TRACER_INITIALIZED = false
31
32 # default configurations
33 unless ap_path and @CONF[:AP_NAME]
34 ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb")
35 end
36 @CONF[:AP_NAME] = File::basename(ap_path, ".rb")
37
38 @CONF[:IRB_NAME] = "irb"
39 @CONF[:IRB_LIB_PATH] = File.dirname(__FILE__)
40
41 @CONF[:RC] = true
42 @CONF[:LOAD_MODULES] = []
43 @CONF[:IRB_RC] = nil
44
45 @CONF[:USE_READLINE] = false unless defined?(ReadlineInputMethod)
46 @CONF[:INSPECT_MODE] = true
47 @CONF[:USE_TRACER] = false
48 @CONF[:USE_LOADER] = false
49 @CONF[:IGNORE_SIGINT] = true
50 @CONF[:IGNORE_EOF] = false
51 @CONF[:ECHO] = nil
52 @CONF[:VERBOSE] = nil
53
54 @CONF[:EVAL_HISTORY] = nil
55 @CONF[:SAVE_HISTORY] = nil
56
57 @CONF[:BACK_TRACE_LIMIT] = 16
58
59 @CONF[:PROMPT] = {
60 :NULL => {
61 :PROMPT_I => nil,
62 :PROMPT_N => nil,
63 :PROMPT_S => nil,
64 :PROMPT_C => nil,
65 :RETURN => "%s\n"
66 },
67 :DEFAULT => {
68 :PROMPT_I => "%N(%m):%03n:%i> ",
69 :PROMPT_N => "%N(%m):%03n:%i> ",
70 :PROMPT_S => "%N(%m):%03n:%i%l ",
71 :PROMPT_C => "%N(%m):%03n:%i* ",
72 :RETURN => "=> %s\n"
73 },
74 :CLASSIC => {
75 :PROMPT_I => "%N(%m):%03n:%i> ",
76 :PROMPT_N => "%N(%m):%03n:%i> ",
77 :PROMPT_S => "%N(%m):%03n:%i%l ",
78 :PROMPT_C => "%N(%m):%03n:%i* ",
79 :RETURN => "%s\n"
80 },
81 :SIMPLE => {
82 :PROMPT_I => ">> ",
83 :PROMPT_N => ">> ",
84 :PROMPT_S => nil,
85 :PROMPT_C => "?> ",
86 :RETURN => "=> %s\n"
87 },
88 :INF_RUBY => {
89 :PROMPT_I => "%N(%m):%03n:%i> ",
90 :PROMPT_N => nil,
91 :PROMPT_S => nil,
92 :PROMPT_C => nil,
93 :RETURN => "%s\n",
94 :AUTO_INDENT => true
95 },
96 :XMP => {
97 :PROMPT_I => nil,
98 :PROMPT_N => nil,
99 :PROMPT_S => nil,
100 :PROMPT_C => nil,
101 :RETURN => " ==>%s\n"
102 }
103 }
104
105 @CONF[:PROMPT_MODE] = (STDIN.tty? ? :DEFAULT : :NULL)
106 @CONF[:AUTO_INDENT] = false
107
108 @CONF[:CONTEXT_MODE] = 3 # use binding in function on TOPLEVEL_BINDING
109 @CONF[:SINGLE_IRB] = false
110
111 @CONF[:LC_MESSAGES] = Locale.new
112
113 @CONF[:AT_EXIT] = []
114
115 @CONF[:DEBUG_LEVEL] = 0
116 end
117
118 def IRB.init_error
119 @CONF[:LC_MESSAGES].load("irb/error.rb")
120 end
121
122 # option analyzing
123 def IRB.parse_opts(argv: ::ARGV)
124 load_path = []
125 while opt = argv.shift
126 case opt
127 when "-f"
128 @CONF[:RC] = false
129 when "-d"
130 $DEBUG = true
131 $VERBOSE = true
132 when "-w"
133 $VERBOSE = true
134 when /^-W(.+)?/
135 opt = $1 || argv.shift
136 case opt
137 when "0"
138 $VERBOSE = nil
139 when "1"
140 $VERBOSE = false
141 else
142 $VERBOSE = true
143 end
144 when /^-r(.+)?/
145 opt = $1 || argv.shift
146 @CONF[:LOAD_MODULES].push opt if opt
147 when /^-I(.+)?/
148 opt = $1 || argv.shift
149 load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt
150 when '-U'
151 set_encoding("UTF-8", "UTF-8")
152 when /^-E(.+)?/, /^--encoding(?:=(.+))?/
153 opt = $1 || argv.shift
154 set_encoding(*opt.split(':', 2))
155 when "--inspect"
156 if /^-/ !~ argv.first
157 @CONF[:INSPECT_MODE] = argv.shift
158 else
159 @CONF[:INSPECT_MODE] = true
160 end
161 when "--noinspect"
162 @CONF[:INSPECT_MODE] = false
163 when "--readline"
164 @CONF[:USE_READLINE] = true
165 when "--noreadline"
166 @CONF[:USE_READLINE] = false
167 when "--echo"
168 @CONF[:ECHO] = true
169 when "--noecho"
170 @CONF[:ECHO] = false
171 when "--verbose"
172 @CONF[:VERBOSE] = true
173 when "--noverbose"
174 @CONF[:VERBOSE] = false
175 when /^--prompt-mode(?:=(.+))?/, /^--prompt(?:=(.+))?/
176 opt = $1 || argv.shift
177 prompt_mode = opt.upcase.tr("-", "_").intern
178 @CONF[:PROMPT_MODE] = prompt_mode
179 when "--noprompt"
180 @CONF[:PROMPT_MODE] = :NULL
181 when "--inf-ruby-mode"
182 @CONF[:PROMPT_MODE] = :INF_RUBY
183 when "--sample-book-mode", "--simple-prompt"
184 @CONF[:PROMPT_MODE] = :SIMPLE
185 when "--tracer"
186 @CONF[:USE_TRACER] = true
187 when /^--back-trace-limit(?:=(.+))?/
188 @CONF[:BACK_TRACE_LIMIT] = ($1 || argv.shift).to_i
189 when /^--context-mode(?:=(.+))?/
190 @CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i
191 when "--single-irb"
192 @CONF[:SINGLE_IRB] = true
193 when /^--irb_debug(?:=(.+))?/
194 @CONF[:DEBUG_LEVEL] = ($1 || argv.shift).to_i
195 when "-v", "--version"
196 print IRB.version, "\n"
197 exit 0
198 when "-h", "--help"
199 require_relative "help"
200 IRB.print_usage
201 exit 0
202 when "--"
203 if opt = argv.shift
204 @CONF[:SCRIPT] = opt
205 $0 = opt
206 end
207 break
208 when /^-/
209 IRB.fail UnrecognizedSwitch, opt
210 else
211 @CONF[:SCRIPT] = opt
212 $0 = opt
213 break
214 end
215 end
216 load_path.collect! do |path|
217 /\A\.\// =~ path ? path : File.expand_path(path)
218 end
219 $LOAD_PATH.unshift(*load_path)
220
221 end
222
223 # running config
224 def IRB.run_config
225 if @CONF[:RC]
226 begin
227 load rc_file
228 rescue LoadError, Errno::ENOENT
229 rescue # StandardError, ScriptError
230 print "load error: #{rc_file}\n"
231 print $!.class, ": ", $!, "\n"
232 for err in $@[0, $@.size - 2]
233 print "\t", err, "\n"
234 end
235 end
236 end
237 end
238
239 IRBRC_EXT = "rc"
240 def IRB.rc_file(ext = IRBRC_EXT)
241 if !@CONF[:RC_NAME_GENERATOR]
242 rc_file_generators do |rcgen|
243 @CONF[:RC_NAME_GENERATOR] ||= rcgen
244 if File.exist?(rcgen.call(IRBRC_EXT))
245 @CONF[:RC_NAME_GENERATOR] = rcgen
246 break
247 end
248 end
249 end
250 case rc_file = @CONF[:RC_NAME_GENERATOR].call(ext)
251 when String
252 return rc_file
253 else
254 IRB.fail IllegalRCNameGenerator
255 end
256 end
257
258 # enumerate possible rc-file base name generators
259 def IRB.rc_file_generators
260 if irbrc = ENV["IRBRC"]
261 yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc}
262 end
263 if home = ENV["HOME"]
264 yield proc{|rc| home+"/.irb#{rc}"}
265 end
266 home = Dir.pwd
267 yield proc{|rc| home+"/.irb#{rc}"}
268 yield proc{|rc| home+"/irb#{rc.sub(/\A_?/, '.')}"}
269 yield proc{|rc| home+"/_irb#{rc}"}
270 yield proc{|rc| home+"/$irb#{rc}"}
271 end
272
273 # loading modules
274 def IRB.load_modules
275 for m in @CONF[:LOAD_MODULES]
276 begin
277 require m
278 rescue LoadError => err
279 warn "#{err.class}: #{err}", uplevel: 0
280 end
281 end
282 end
283
284
285 DefaultEncodings = Struct.new(:external, :internal)
286 class << IRB
287 private
288 def set_encoding(extern, intern = nil)
289 verbose, $VERBOSE = $VERBOSE, nil
290 Encoding.default_external = extern unless extern.nil? || extern.empty?
291 Encoding.default_internal = intern unless intern.nil? || intern.empty?
292 @CONF[:ENCODINGS] = IRB::DefaultEncodings.new(extern, intern)
293 [$stdin, $stdout, $stderr].each do |io|
294 io.set_encoding(extern, intern)
295 end
296 @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern)
297 ensure
298 $VERBOSE = verbose
299 end
300 end
301 end
+0
-192
vendor/irb/input-method.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/input-method.rb - input methods used irb
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 require_relative 'src_encoding'
12 require_relative 'magic-file'
13
14 module IRB
15 STDIN_FILE_NAME = "(line)" # :nodoc:
16 class InputMethod
17
18 # Creates a new input method object
19 def initialize(file = STDIN_FILE_NAME)
20 @file_name = file
21 end
22 # The file name of this input method, usually given during initialization.
23 attr_reader :file_name
24
25 # The irb prompt associated with this input method
26 attr_accessor :prompt
27
28 # Reads the next line from this input method.
29 #
30 # See IO#gets for more information.
31 def gets
32 IRB.fail NotImplementedError, "gets"
33 end
34 public :gets
35
36 # Whether this input method is still readable when there is no more data to
37 # read.
38 #
39 # See IO#eof for more information.
40 def readable_after_eof?
41 false
42 end
43 end
44
45 class StdioInputMethod < InputMethod
46 # Creates a new input method object
47 def initialize
48 super
49 @line_no = 0
50 @line = []
51 @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
52 @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
53 end
54
55 # Reads the next line from this input method.
56 #
57 # See IO#gets for more information.
58 def gets
59 print @prompt
60 line = @stdin.gets
61 @line[@line_no += 1] = line
62 end
63
64 # Whether the end of this input method has been reached, returns +true+ if
65 # there is no more data to read.
66 #
67 # See IO#eof? for more information.
68 def eof?
69 @stdin.eof?
70 end
71
72 # Whether this input method is still readable when there is no more data to
73 # read.
74 #
75 # See IO#eof for more information.
76 def readable_after_eof?
77 true
78 end
79
80 # Returns the current line number for #io.
81 #
82 # #line counts the number of times #gets is called.
83 #
84 # See IO#lineno for more information.
85 def line(line_no)
86 @line[line_no]
87 end
88
89 # The external encoding for standard input.
90 def encoding
91 @stdin.external_encoding
92 end
93 end
94
95 # Use a File for IO with irb, see InputMethod
96 class FileInputMethod < InputMethod
97 # Creates a new input method object
98 def initialize(file)
99 super
100 @io = IRB::MagicFile.open(file)
101 end
102 # The file name of this input method, usually given during initialization.
103 attr_reader :file_name
104
105 # Whether the end of this input method has been reached, returns +true+ if
106 # there is no more data to read.
107 #
108 # See IO#eof? for more information.
109 def eof?
110 @io.eof?
111 end
112
113 # Reads the next line from this input method.
114 #
115 # See IO#gets for more information.
116 def gets
117 print @prompt
118 l = @io.gets
119 l
120 end
121
122 # The external encoding for standard input.
123 def encoding
124 @io.external_encoding
125 end
126 end
127
128 begin
129 require "readline"
130 class ReadlineInputMethod < InputMethod
131 include Readline
132 # Creates a new input method object using Readline
133 def initialize
134 super
135
136 @line_no = 0
137 @line = []
138 @eof = false
139
140 @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
141 @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
142 end
143
144 # Reads the next line from this input method.
145 #
146 # See IO#gets for more information.
147 def gets
148 Readline.input = @stdin
149 Readline.output = @stdout
150 if l = readline(@prompt, false)
151 HISTORY.push(l) if !l.empty?
152 @line[@line_no += 1] = l + "\n"
153 else
154 @eof = true
155 l
156 end
157 end
158
159 # Whether the end of this input method has been reached, returns +true+
160 # if there is no more data to read.
161 #
162 # See IO#eof? for more information.
163 def eof?
164 @eof
165 end
166
167 # Whether this input method is still readable when there is no more data to
168 # read.
169 #
170 # See IO#eof for more information.
171 def readable_after_eof?
172 true
173 end
174
175 # Returns the current line number for #io.
176 #
177 # #line counts the number of times #gets is called.
178 #
179 # See IO#lineno for more information.
180 def line(line_no)
181 @line[line_no]
182 end
183
184 # The external encoding for standard input.
185 def encoding
186 @stdin.external_encoding
187 end
188 end
189 rescue LoadError
190 end
191 end
+0
-132
vendor/irb/inspector.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/inspector.rb - inspect methods
3 # $Release Version: 0.9.6$
4 # $Revision: 1.19 $
5 # $Date: 2002/06/11 07:51:31 $
6 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
7 #
8 # --
9 #
10 #
11 #
12
13 module IRB # :nodoc:
14
15
16 # Convenience method to create a new Inspector, using the given +inspect+
17 # proc, and optional +init+ proc and passes them to Inspector.new
18 #
19 # irb(main):001:0> ins = IRB::Inspector(proc{ |v| "omg! #{v}" })
20 # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28>
21 # irb(main):001:0> "what?" #=> omg! what?
22 #
23 def IRB::Inspector(inspect, init = nil)
24 Inspector.new(inspect, init)
25 end
26
27 # An irb inspector
28 #
29 # In order to create your own custom inspector there are two things you
30 # should be aware of:
31 #
32 # Inspector uses #inspect_value, or +inspect_proc+, for output of return values.
33 #
34 # This also allows for an optional #init+, or +init_proc+, which is called
35 # when the inspector is activated.
36 #
37 # Knowing this, you can create a rudimentary inspector as follows:
38 #
39 # irb(main):001:0> ins = IRB::Inspector.new(proc{ |v| "omg! #{v}" })
40 # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28>
41 # irb(main):001:0> "what?" #=> omg! what?
42 #
43 class Inspector
44 # Default inspectors available to irb, this includes:
45 #
46 # +:pp+:: Using Kernel#pretty_inspect
47 # +:yaml+:: Using YAML.dump
48 # +:marshal+:: Using Marshal.dump
49 INSPECTORS = {}
50
51 # Determines the inspector to use where +inspector+ is one of the keys passed
52 # during inspector definition.
53 def self.keys_with_inspector(inspector)
54 INSPECTORS.select{|k,v| v == inspector}.collect{|k, v| k}
55 end
56
57 # Example
58 #
59 # Inspector.def_inspector(key, init_p=nil){|v| v.inspect}
60 # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect}
61 # Inspector.def_inspector(key, inspector)
62 # Inspector.def_inspector([key1,...], inspector)
63 def self.def_inspector(key, arg=nil, &block)
64 if block_given?
65 inspector = IRB::Inspector(block, arg)
66 else
67 inspector = arg
68 end
69
70 case key
71 when Array
72 for k in key
73 def_inspector(k, inspector)
74 end
75 when Symbol
76 INSPECTORS[key] = inspector
77 INSPECTORS[key.to_s] = inspector
78 when String
79 INSPECTORS[key] = inspector
80 INSPECTORS[key.intern] = inspector
81 else
82 INSPECTORS[key] = inspector
83 end
84 end
85
86 # Creates a new inspector object, using the given +inspect_proc+ when
87 # output return values in irb.
88 def initialize(inspect_proc, init_proc = nil)
89 @init = init_proc
90 @inspect = inspect_proc
91 end
92
93 # Proc to call when the inspector is activated, good for requiring
94 # dependent libraries.
95 def init
96 @init.call if @init
97 end
98
99 # Proc to call when the input is evaluated and output in irb.
100 def inspect_value(v)
101 @inspect.call(v)
102 end
103 end
104
105 Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s}
106 Inspector.def_inspector([true, :p, :inspect]){|v|
107 begin
108 v.inspect
109 rescue NoMethodError
110 puts "(Object doesn't support #inspect)"
111 end
112 }
113 Inspector.def_inspector([:pp, :pretty_inspect], proc{require "pp"}){|v| v.pretty_inspect.chomp}
114 Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
115 begin
116 YAML.dump(v)
117 rescue
118 puts "(can't dump yaml. use inspect)"
119 v.inspect
120 end
121 }
122
123 Inspector.def_inspector([:marshal, :Marshal, :MARSHAL, Marshal]){|v|
124 Marshal.dump(v)
125 }
126 end
127
128
129
130
131
+0
-4
vendor/irb/lc/.document less more
0 # hide help-message files which contain usage information
1 error.rb
2 ja/encoding_aliases.rb
3 ja/error.rb
+0
-32
vendor/irb/lc/error.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/lc/error.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 require "e2mmap"
12
13 # :stopdoc:
14 module IRB
15
16 # exceptions
17 extend Exception2MessageMapper
18 def_exception :UnrecognizedSwitch, "Unrecognized switch: %s"
19 def_exception :NotImplementedError, "Need to define `%s'"
20 def_exception :CantReturnToNormalMode, "Can't return to normal mode."
21 def_exception :IllegalParameter, "Invalid parameter(%s)."
22 def_exception :IrbAlreadyDead, "Irb is already dead."
23 def_exception :IrbSwitchedToCurrentThread, "Switched to current thread."
24 def_exception :NoSuchJob, "No such job(%s)."
25 def_exception :CantShiftToMultiIrbMode, "Can't shift to multi irb mode."
26 def_exception :CantChangeBinding, "Can't change binding to (%s)."
27 def_exception :UndefinedPromptMode, "Undefined prompt mode(%s)."
28 def_exception :IllegalRCGenerator, 'Define illegal RC_NAME_GENERATOR.'
29
30 end
31 # :startdoc:
+0
-49
vendor/irb/lc/help-message less more
0 # -*- coding: utf-8 -*-
1 #
2 # irb/lc/help-message.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 Usage: irb.rb [options] [programfile] [arguments]
12 -f Suppress read of ~/.irbrc
13 -d Set $DEBUG to true (same as `ruby -d')
14 -r load-module Same as `ruby -r'
15 -I path Specify $LOAD_PATH directory
16 -U Same as `ruby -U`
17 -E enc Same as `ruby -E`
18 -w Same as `ruby -w`
19 -W[level=2] Same as `ruby -W`
20 --context-mode n Set n[0-3] to method to create Binding Object,
21 when new workspace was created
22 --echo Show result(default)
23 --noecho Don't show result
24 --inspect Use `inspect' for output (default except for bc mode)
25 --noinspect Don't use inspect for output
26 --readline Use Readline extension module
27 --noreadline Don't use Readline extension module
28 --prompt prompt-mode/--prompt-mode prompt-mode
29 Switch prompt mode. Pre-defined prompt modes are
30 `default', `simple', `xmp' and `inf-ruby'
31 --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
32 Suppresses --readline.
33 --sample-book-mode/--simple-prompt
34 Simple prompt mode
35 --noprompt No prompt mode
36 --single-irb Share self with sub-irb.
37 --tracer Display trace for each execution of commands.
38 --back-trace-limit n
39 Display backtrace top n and tail n. The default
40 value is 16.
41 --irb_debug n Set internal debug level to n (not for popular use)
42 --verbose Show details
43 --noverbose Don't show details
44 -v, --version Print the version of irb
45 -h, --help Print help
46 -- Separate options of irb from the list of command-line args
47
48 # vim:fileencoding=utf-8
+0
-11
vendor/irb/lc/ja/encoding_aliases.rb less more
0 # frozen_string_literal: false
1 # :stopdoc:
2 module IRB
3 class Locale
4 @@legacy_encoding_alias_map = {
5 'ujis' => Encoding::EUC_JP,
6 'euc' => Encoding::EUC_JP
7 }.freeze
8 end
9 end
10 # :startdoc:
+0
-31
vendor/irb/lc/ja/error.rb less more
0 # -*- coding: utf-8 -*-
1 # frozen_string_literal: false
2 # irb/lc/ja/error.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 require "e2mmap"
12
13 # :stopdoc:
14 module IRB
15 # exceptions
16 extend Exception2MessageMapper
17 def_exception :UnrecognizedSwitch, 'スイッチ(%s)が分りません'
18 def_exception :NotImplementedError, '`%s\'の定義が必要です'
19 def_exception :CantReturnToNormalMode, 'Normalモードに戻れません.'
20 def_exception :IllegalParameter, 'パラメータ(%s)が間違っています.'
21 def_exception :IrbAlreadyDead, 'Irbは既に死んでいます.'
22 def_exception :IrbSwitchedToCurrentThread, 'カレントスレッドに切り替わりました.'
23 def_exception :NoSuchJob, 'そのようなジョブ(%s)はありません.'
24 def_exception :CantShiftToMultiIrbMode, 'multi-irb modeに移れません.'
25 def_exception :CantChangeBinding, 'バインディング(%s)に変更できません.'
26 def_exception :UndefinedPromptMode, 'プロンプトモード(%s)は定義されていません.'
27 def_exception :IllegalRCNameGenerator, 'RC_NAME_GENERATORが正しく定義されていません.'
28 end
29 # :startdoc:
30 # vim:fileencoding=utf-8
+0
-52
vendor/irb/lc/ja/help-message less more
0 # -*- coding: utf-8 -*-
1 # irb/lc/ja/help-message.rb -
2 # $Release Version: 0.9.6$
3 # $Revision$
4 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
5 #
6 # --
7 #
8 #
9 #
10 Usage: irb.rb [options] [programfile] [arguments]
11 -f ~/.irbrc を読み込まない.
12 -d $DEBUG をtrueにする(ruby -d と同じ)
13 -r load-module ruby -r と同じ.
14 -I path $LOAD_PATH に path を追加する.
15 -U ruby -U と同じ.
16 -E enc ruby -E と同じ.
17 -w ruby -w と同じ.
18 -W[level=2] ruby -W と同じ.
19 --context-mode n 新しいワークスペースを作成した時に関連する Binding
20 オブジェクトの作成方法を 0 から 3 のいずれかに設定する.
21 --echo 実行結果を表示する(デフォルト).
22 --noecho 実行結果を表示しない.
23 --inspect 結果出力にinspectを用いる(bcモード以外はデフォルト).
24 --noinspect 結果出力にinspectを用いない.
25 --readline readlineライブラリを利用する.
26 --noreadline readlineライブラリを利用しない.
27 --prompt prompt-mode/--prompt-mode prompt-mode
28 プロンプトモードを切替えます. 現在定義されているプ
29 ロンプトモードは, default, simple, xmp, inf-rubyが
30 用意されています.
31 --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特
32 に指定がない限り, readlineライブラリは使わなくなる.
33 --sample-book-mode/--simple-prompt
34 非常にシンプルなプロンプトを用いるモードです.
35 --noprompt プロンプト表示を行なわない.
36 --single-irb irb 中で self を実行して得られるオブジェクトをサ
37 ブ irb と共有する.
38 --tracer コマンド実行時にトレースを行なう.
39 --back-trace-limit n
40 バックトレース表示をバックトレースの頭から n, 後ろ
41 からnだけ行なう. デフォルトは16
42
43 --irb_debug n irbのデバッグレベルをnに設定する(非推奨).
44
45 --verbose 詳細なメッセージを出力する.
46 --noverbose 詳細なメッセージを出力しない(デフォルト).
47 -v, --version irbのバージョンを表示する.
48 -h, --help irb のヘルプを表示する.
49 -- 以降のコマンドライン引数をオプションとして扱わない.
50
51 # vim:fileencoding=utf-8
+0
-182
vendor/irb/locale.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/locale.rb - internationalization module
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 module IRB # :nodoc:
12 class Locale
13
14 LOCALE_NAME_RE = %r[
15 (?<language>[[:alpha:]]{2,3})
16 (?:_ (?<territory>[[:alpha:]]{2,3}) )?
17 (?:\. (?<codeset>[^@]+) )?
18 (?:@ (?<modifier>.*) )?
19 ]x
20 LOCALE_DIR = "/lc/"
21
22 @@legacy_encoding_alias_map = {}.freeze
23
24 def initialize(locale = nil)
25 @lang = @territory = @encoding_name = @modifier = nil
26 @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C"
27 if m = LOCALE_NAME_RE.match(@locale)
28 @lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier]
29
30 if @encoding_name
31 begin load 'irb/encoding_aliases.rb'; rescue LoadError; end
32 if @encoding = @@legacy_encoding_alias_map[@encoding_name]
33 warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1)
34 end
35 @encoding = Encoding.find(@encoding_name) rescue nil
36 end
37 end
38 @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT)
39 end
40
41 attr_reader :lang, :territory, :encoding, :modifier
42
43 def String(mes)
44 mes = super(mes)
45 if @encoding
46 mes.encode(@encoding, undef: :replace)
47 else
48 mes
49 end
50 end
51
52 def format(*opts)
53 String(super(*opts))
54 end
55
56 def gets(*rs)
57 String(super(*rs))
58 end
59
60 def readline(*rs)
61 String(super(*rs))
62 end
63
64 def print(*opts)
65 ary = opts.collect{|opt| String(opt)}
66 super(*ary)
67 end
68
69 def printf(*opts)
70 s = format(*opts)
71 print s
72 end
73
74 def puts(*opts)
75 ary = opts.collect{|opt| String(opt)}
76 super(*ary)
77 end
78
79 def require(file, priv = nil)
80 rex = Regexp.new("lc/#{Regexp.quote(file)}\.(so|o|sl|rb)?")
81 return false if $".find{|f| f =~ rex}
82
83 case file
84 when /\.rb$/
85 begin
86 load(file, priv)
87 $".push file
88 return true
89 rescue LoadError
90 end
91 when /\.(so|o|sl)$/
92 return super
93 end
94
95 begin
96 load(f = file + ".rb")
97 $".push f #"
98 return true
99 rescue LoadError
100 return ruby_require(file)
101 end
102 end
103
104 alias toplevel_load load
105
106 def load(file, priv=nil)
107 found = find(file)
108 if found
109 return real_load(found, priv)
110 else
111 raise LoadError, "No such file to load -- #{file}"
112 end
113 end
114
115 def find(file , paths = $:)
116 dir = File.dirname(file)
117 dir = "" if dir == "."
118 base = File.basename(file)
119
120 if dir.start_with?('/')
121 return each_localized_path(dir, base).find{|full_path| File.readable? full_path}
122 else
123 return search_file(paths, dir, base)
124 end
125 end
126
127 private
128 def real_load(path, priv)
129 src = MagicFile.open(path){|f| f.read}
130 if priv
131 eval("self", TOPLEVEL_BINDING).extend(Module.new {eval(src, nil, path)})
132 else
133 eval(src, TOPLEVEL_BINDING, path)
134 end
135 end
136
137 # @param paths load paths in which IRB find a localized file.
138 # @param dir directory
139 # @param file basename to be localized
140 #
141 # typically, for the parameters and a <path> in paths, it searches
142 # <path>/<dir>/<locale>/<file>
143 def search_file(lib_paths, dir, file)
144 each_localized_path(dir, file) do |lc_path|
145 lib_paths.each do |libpath|
146 full_path = File.join(libpath, lc_path)
147 return full_path if File.readable?(full_path)
148 end
149 redo if defined?(Gem) and Gem.try_activate(lc_path)
150 end
151 nil
152 end
153
154 def each_localized_path(dir, file)
155 return enum_for(:each_localized_path) unless block_given?
156 each_sublocale do |lc|
157 yield lc.nil? ? File.join(dir, LOCALE_DIR, file) : File.join(dir, LOCALE_DIR, lc, file)
158 end
159 end
160
161 def each_sublocale
162 if @lang
163 if @territory
164 if @encoding_name
165 yield "#{@lang}_#{@territory}.#{@encoding_name}@#{@modifier}" if @modifier
166 yield "#{@lang}_#{@territory}.#{@encoding_name}"
167 end
168 yield "#{@lang}_#{@territory}@#{@modifier}" if @modifier
169 yield "#{@lang}_#{@territory}"
170 end
171 if @encoding_name
172 yield "#{@lang}.#{@encoding_name}@#{@modifier}" if @modifier
173 yield "#{@lang}.#{@encoding_name}"
174 end
175 yield "#{@lang}@#{@modifier}" if @modifier
176 yield "#{@lang}"
177 end
178 yield nil
179 end
180 end
181 end
+0
-38
vendor/irb/magic-file.rb less more
0 # frozen_string_literal: false
1 module IRB
2 class << (MagicFile = Object.new)
3 # see parser_magic_comment in parse.y
4 ENCODING_SPEC_RE = %r"coding\s*[=:]\s*([[:alnum:]\-_]+)"
5
6 def open(path)
7 io = File.open(path, 'rb')
8 line = io.gets
9 line = io.gets if line[0,2] == "#!"
10 encoding = detect_encoding(line)
11 internal_encoding = encoding
12 encoding ||= IRB.default_src_encoding
13 io.rewind
14 io.set_encoding(encoding, internal_encoding)
15
16 if block_given?
17 begin
18 return (yield io)
19 ensure
20 io.close
21 end
22 else
23 return io
24 end
25 end
26
27 private
28 def detect_encoding(line)
29 return unless line[0] == ?#
30 line = line[1..-1]
31 line = $1 if line[/-\*-\s*(.*?)\s*-*-$/]
32 return nil unless ENCODING_SPEC_RE =~ line
33 encoding = $1
34 return encoding.sub(/-(?:mac|dos|unix)/i, '')
35 end
36 end
37 end
+0
-232
vendor/irb/notifier.rb less more
0 # frozen_string_literal: false
1 #
2 # notifier.rb - output methods used by irb
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require "e2mmap"
13 require_relative "output-method"
14
15 module IRB
16 # An output formatter used internally by the lexer.
17 module Notifier
18 extend Exception2MessageMapper
19 def_exception :ErrUndefinedNotifier,
20 "undefined notifier level: %d is specified"
21 def_exception :ErrUnrecognizedLevel,
22 "unrecognized notifier level: %s is specified"
23
24 # Define a new Notifier output source, returning a new CompositeNotifier
25 # with the given +prefix+ and +output_method+.
26 #
27 # The optional +prefix+ will be appended to all objects being inspected
28 # during output, using the given +output_method+ as the output source. If
29 # no +output_method+ is given, StdioOutputMethod will be used, and all
30 # expressions will be sent directly to STDOUT without any additional
31 # formatting.
32 def def_notifier(prefix = "", output_method = StdioOutputMethod.new)
33 CompositeNotifier.new(prefix, output_method)
34 end
35 module_function :def_notifier
36
37 # An abstract class, or superclass, for CompositeNotifier and
38 # LeveledNotifier to inherit. It provides several wrapper methods for the
39 # OutputMethod object used by the Notifier.
40 class AbstractNotifier
41 # Creates a new Notifier object
42 def initialize(prefix, base_notifier)
43 @prefix = prefix
44 @base_notifier = base_notifier
45 end
46
47 # The +prefix+ for this Notifier, which is appended to all objects being
48 # inspected during output.
49 attr_reader :prefix
50
51 # A wrapper method used to determine whether notifications are enabled.
52 #
53 # Defaults to +true+.
54 def notify?
55 true
56 end
57
58 # See OutputMethod#print for more detail.
59 def print(*opts)
60 @base_notifier.print prefix, *opts if notify?
61 end
62
63 # See OutputMethod#printn for more detail.
64 def printn(*opts)
65 @base_notifier.printn prefix, *opts if notify?
66 end
67
68 # See OutputMethod#printf for more detail.
69 def printf(format, *opts)
70 @base_notifier.printf(prefix + format, *opts) if notify?
71 end
72
73 # See OutputMethod#puts for more detail.
74 def puts(*objs)
75 if notify?
76 @base_notifier.puts(*objs.collect{|obj| prefix + obj.to_s})
77 end
78 end
79
80 # Same as #ppx, except it uses the #prefix given during object
81 # initialization.
82 # See OutputMethod#ppx for more detail.
83 def pp(*objs)
84 if notify?
85 @base_notifier.ppx @prefix, *objs
86 end
87 end
88
89 # Same as #pp, except it concatenates the given +prefix+ with the #prefix
90 # given during object initialization.
91 #
92 # See OutputMethod#ppx for more detail.
93 def ppx(prefix, *objs)
94 if notify?
95 @base_notifier.ppx @prefix+prefix, *objs
96 end
97 end
98
99 # Execute the given block if notifications are enabled.
100 def exec_if
101 yield(@base_notifier) if notify?
102 end
103 end
104
105 # A class that can be used to create a group of notifier objects with the
106 # intent of representing a leveled notification system for irb.
107 #
108 # This class will allow you to generate other notifiers, and assign them
109 # the appropriate level for output.
110 #
111 # The Notifier class provides a class-method Notifier.def_notifier to
112 # create a new composite notifier. Using the first composite notifier
113 # object you create, sibling notifiers can be initialized with
114 # #def_notifier.
115 class CompositeNotifier < AbstractNotifier
116 # Create a new composite notifier object with the given +prefix+, and
117 # +base_notifier+ to use for output.
118 def initialize(prefix, base_notifier)
119 super
120
121 @notifiers = [D_NOMSG]
122 @level_notifier = D_NOMSG
123 end
124
125 # List of notifiers in the group
126 attr_reader :notifiers
127
128 # Creates a new LeveledNotifier in the composite #notifiers group.
129 #
130 # The given +prefix+ will be assigned to the notifier, and +level+ will
131 # be used as the index of the #notifiers Array.
132 #
133 # This method returns the newly created instance.
134 def def_notifier(level, prefix = "")
135 notifier = LeveledNotifier.new(self, level, prefix)
136 @notifiers[level] = notifier
137 notifier
138 end
139
140 # Returns the leveled notifier for this object
141 attr_reader :level_notifier
142 alias level level_notifier
143
144 # Sets the leveled notifier for this object.
145 #
146 # When the given +value+ is an instance of AbstractNotifier,
147 # #level_notifier is set to the given object.
148 #
149 # When an Integer is given, #level_notifier is set to the notifier at the
150 # index +value+ in the #notifiers Array.
151 #
152 # If no notifier exists at the index +value+ in the #notifiers Array, an
153 # ErrUndefinedNotifier exception is raised.
154 #
155 # An ErrUnrecognizedLevel exception is raised if the given +value+ is not
156 # found in the existing #notifiers Array, or an instance of
157 # AbstractNotifier
158 def level_notifier=(value)
159 case value
160 when AbstractNotifier
161 @level_notifier = value
162 when Integer
163 l = @notifiers[value]
164 Notifier.Raise ErrUndefinedNotifier, value unless l
165 @level_notifier = l
166 else
167 Notifier.Raise ErrUnrecognizedLevel, value unless l
168 end
169 end
170
171 alias level= level_notifier=
172 end
173
174 # A leveled notifier is comparable to the composite group from
175 # CompositeNotifier#notifiers.
176 class LeveledNotifier < AbstractNotifier
177 include Comparable
178
179 # Create a new leveled notifier with the given +base+, and +prefix+ to
180 # send to AbstractNotifier.new
181 #
182 # The given +level+ is used to compare other leveled notifiers in the
183 # CompositeNotifier group to determine whether or not to output
184 # notifications.
185 def initialize(base, level, prefix)
186 super(prefix, base)
187
188 @level = level
189 end
190
191 # The current level of this notifier object
192 attr_reader :level
193
194 # Compares the level of this notifier object with the given +other+
195 # notifier.
196 #
197 # See the Comparable module for more information.
198 def <=>(other)
199 @level <=> other.level
200 end
201
202 # Whether to output messages to the output method, depending on the level
203 # of this notifier object.
204 def notify?
205 @base_notifier.level >= self
206 end
207 end
208
209 # NoMsgNotifier is a LeveledNotifier that's used as the default notifier
210 # when creating a new CompositeNotifier.
211 #
212 # This notifier is used as the +zero+ index, or level +0+, for
213 # CompositeNotifier#notifiers, and will not output messages of any sort.
214 class NoMsgNotifier < LeveledNotifier
215 # Creates a new notifier that should not be used to output messages.
216 def initialize
217 @base_notifier = nil
218 @level = 0
219 @prefix = ""
220 end
221
222 # Ensures notifications are ignored, see AbstractNotifier#notify? for
223 # more information.
224 def notify?
225 false
226 end
227 end
228
229 D_NOMSG = NoMsgNotifier.new # :nodoc:
230 end
231 end
+0
-92
vendor/irb/output-method.rb less more
0 # frozen_string_literal: false
1 #
2 # output-method.rb - output methods used by irb
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require "e2mmap"
13
14 module IRB
15 # An abstract output class for IO in irb. This is mainly used internally by
16 # IRB::Notifier. You can define your own output method to use with Irb.new,
17 # or Context.new
18 class OutputMethod
19 extend Exception2MessageMapper
20 def_exception :NotImplementedError, "Need to define `%s'"
21
22
23 # Open this method to implement your own output method, raises a
24 # NotImplementedError if you don't define #print in your own class.
25 def print(*opts)
26 OutputMethod.Raise NotImplementedError, "print"
27 end
28
29 # Prints the given +opts+, with a newline delimiter.
30 def printn(*opts)
31 print opts.join(" "), "\n"
32 end
33
34 # Extends IO#printf to format the given +opts+ for Kernel#sprintf using
35 # #parse_printf_format
36 def printf(format, *opts)
37 if /(%*)%I/ =~ format
38 format, opts = parse_printf_format(format, opts)
39 end
40 print sprintf(format, *opts)
41 end
42
43 # Returns an array of the given +format+ and +opts+ to be used by
44 # Kernel#sprintf, if there was a successful Regexp match in the given
45 # +format+ from #printf
46 #
47 # %
48 # <flag> [#0- +]
49 # <minimum field width> (\*|\*[1-9][0-9]*\$|[1-9][0-9]*)
50 # <precision>.(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)?
51 # #<length modifier>(hh|h|l|ll|L|q|j|z|t)
52 # <conversion specifier>[diouxXeEfgGcsb%]
53 def parse_printf_format(format, opts)
54 return format, opts if $1.size % 2 == 1
55 end
56
57 # Calls #print on each element in the given +objs+, followed by a newline
58 # character.
59 def puts(*objs)
60 for obj in objs
61 print(*obj)
62 print "\n"
63 end
64 end
65
66 # Prints the given +objs+ calling Object#inspect on each.
67 #
68 # See #puts for more detail.
69 def pp(*objs)
70 puts(*objs.collect{|obj| obj.inspect})
71 end
72
73 # Prints the given +objs+ calling Object#inspect on each and appending the
74 # given +prefix+.
75 #
76 # See #puts for more detail.
77 def ppx(prefix, *objs)
78 puts(*objs.collect{|obj| prefix+obj.inspect})
79 end
80
81 end
82
83 # A standard output printer
84 class StdioOutputMethod < OutputMethod
85 # Prints the given +opts+ to standard output, see IO#print for more
86 # information.
87 def print(*opts)
88 STDOUT.print(*opts)
89 end
90 end
91 end
+0
-1180
vendor/irb/ruby-lex.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/ruby-lex.rb - ruby lexcal analyzer
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require "e2mmap"
13 require_relative "slex"
14 require_relative "ruby-token"
15
16 # :stopdoc:
17 class RubyLex
18
19 extend Exception2MessageMapper
20 def_exception(:AlreadyDefinedToken, "Already defined token(%s)")
21 def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')")
22 def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')")
23 def_exception(:TkReading2TokenDuplicateError,
24 "key duplicate(token_n='%s', key='%s')")
25 def_exception(:SyntaxError, "%s")
26
27 def_exception(:TerminateLineInput, "Terminate Line Input")
28
29 include RubyToken
30
31 class << self
32 attr_accessor :debug_level
33 def debug?
34 @debug_level > 0
35 end
36 end
37 @debug_level = 0
38
39 def initialize
40 lex_init
41 set_input(STDIN)
42
43 @seek = 0
44 @exp_line_no = @line_no = 1
45 @base_char_no = 0
46 @char_no = 0
47 @rests = []
48 @readed = []
49 @here_readed = []
50
51 @indent = 0
52 @indent_stack = []
53 @lex_state = EXPR_BEG
54 @space_seen = false
55 @here_header = false
56 @post_symbeg = false
57
58 @continue = false
59 @line = ""
60
61 @skip_space = false
62 @readed_auto_clean_up = false
63 @exception_on_syntax_error = true
64
65 @prompt = nil
66 end
67
68 attr_accessor :skip_space
69 attr_accessor :readed_auto_clean_up
70 attr_accessor :exception_on_syntax_error
71
72 attr_reader :seek
73 attr_reader :char_no
74 attr_reader :line_no
75 attr_reader :indent
76
77 # io functions
78 def set_input(io, p = nil, &block)
79 @io = io
80 if p.respond_to?(:call)
81 @input = p
82 elsif block_given?
83 @input = block
84 else
85 @input = Proc.new{@io.gets}
86 end
87 end
88
89 def get_readed
90 if idx = @readed.rindex("\n")
91 @base_char_no = @readed.size - (idx + 1)
92 else
93 @base_char_no += @readed.size
94 end
95
96 readed = @readed.join("")
97 @readed = []
98 readed
99 end
100
101 def getc
102 while @rests.empty?
103 @rests.push nil unless buf_input
104 end
105 c = @rests.shift
106 if @here_header
107 @here_readed.push c
108 else
109 @readed.push c
110 end
111 @seek += 1
112 if c == "\n"
113 @line_no += 1
114 @char_no = 0
115 else
116 @char_no += 1
117 end
118 c
119 end
120
121 def gets
122 l = ""
123 while c = getc
124 l.concat(c)
125 break if c == "\n"
126 end
127 return nil if l == "" and c.nil?
128 l
129 end
130
131 def eof?
132 @io.eof?
133 end
134
135 def getc_of_rests
136 if @rests.empty?
137 nil
138 else
139 getc
140 end
141 end
142
143 def ungetc(c = nil)
144 if @here_readed.empty?
145 c2 = @readed.pop
146 else
147 c2 = @here_readed.pop
148 end
149 c = c2 unless c
150 @rests.unshift c #c =
151 @seek -= 1
152 if c == "\n"
153 @line_no -= 1
154 if idx = @readed.rindex("\n")
155 @char_no = idx + 1
156 else
157 @char_no = @base_char_no + @readed.size
158 end
159 else
160 @char_no -= 1
161 end
162 end
163
164 def peek_equal?(str)
165 chrs = str.split(//)
166 until @rests.size >= chrs.size
167 return false unless buf_input
168 end
169 @rests[0, chrs.size] == chrs
170 end
171
172 def peek_match?(regexp)
173 while @rests.empty?
174 return false unless buf_input
175 end
176 regexp =~ @rests.join("")
177 end
178
179 def peek(i = 0)
180 while @rests.size <= i
181 return nil unless buf_input
182 end
183 @rests[i]
184 end
185
186 def buf_input
187 prompt
188 line = @input.call
189 return nil unless line
190 @rests.concat line.chars.to_a
191 true
192 end
193 private :buf_input
194
195 def set_prompt(p = nil, &block)
196 p = block if block_given?
197 if p.respond_to?(:call)
198 @prompt = p
199 else
200 @prompt = Proc.new{print p}
201 end
202 end
203
204 def prompt
205 if @prompt
206 @prompt.call(@ltype, @indent, @continue, @line_no)
207 end
208 end
209
210 def initialize_input
211 @ltype = nil
212 @quoted = nil
213 @indent = 0
214 @indent_stack = []
215 @lex_state = EXPR_BEG
216 @space_seen = false
217 @here_header = false
218
219 @continue = false
220 @post_symbeg = false
221
222 prompt
223
224 @line = ""
225 @exp_line_no = @line_no
226 end
227
228 def each_top_level_statement
229 initialize_input
230 catch(:TERM_INPUT) do
231 loop do
232 begin
233 @continue = false
234 prompt
235 unless l = lex
236 throw :TERM_INPUT if @line == ''
237 else
238 @line.concat l
239 if @ltype or @continue or @indent > 0
240 next
241 end
242 end
243 if @line != "\n"
244 @line.force_encoding(@io.encoding)
245 yield @line, @exp_line_no
246 end
247 break unless l
248 @line = ''
249 @exp_line_no = @line_no
250
251 @indent = 0
252 @indent_stack = []
253 prompt
254 rescue TerminateLineInput
255 initialize_input
256 prompt
257 get_readed
258 end
259 end
260 end
261 end
262
263 def lex
264 continue = @continue
265 while tk = token
266 case tk
267 when TkNL, TkEND_OF_SCRIPT
268 @continue = continue unless continue.nil?
269 break unless @continue
270 when TkSPACE, TkCOMMENT
271 when TkSEMICOLON, TkBEGIN, TkELSE
272 @continue = continue = false
273 else
274 continue = nil
275 end
276 end
277 line = get_readed
278 if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil?
279 nil
280 else
281 line
282 end
283 end
284
285 def token
286 @prev_seek = @seek
287 @prev_line_no = @line_no
288 @prev_char_no = @char_no
289 begin
290 begin
291 tk = @OP.match(self)
292 @space_seen = tk.kind_of?(TkSPACE)
293 @lex_state = EXPR_END if @post_symbeg && tk.kind_of?(TkOp)
294 @post_symbeg = tk.kind_of?(TkSYMBEG)
295 rescue SyntaxError
296 raise if @exception_on_syntax_error
297 tk = TkError.new(@seek, @line_no, @char_no)
298 end
299 end while @skip_space and tk.kind_of?(TkSPACE)
300 if @readed_auto_clean_up
301 get_readed
302 end
303 tk
304 end
305
306 ENINDENT_CLAUSE = [
307 "case", "class", "def", "do", "for", "if",
308 "module", "unless", "until", "while", "begin"
309 ]
310 DEINDENT_CLAUSE = ["end"
311 ]
312
313 PERCENT_LTYPE = {
314 "q" => "\'",
315 "Q" => "\"",
316 "x" => "\`",
317 "r" => "/",
318 "w" => "]",
319 "W" => "]",
320 "i" => "]",
321 "I" => "]",
322 "s" => ":"
323 }
324
325 PERCENT_PAREN = {
326 "{" => "}",
327 "[" => "]",
328 "<" => ">",
329 "(" => ")"
330 }
331
332 Ltype2Token = {
333 "\'" => TkSTRING,
334 "\"" => TkSTRING,
335 "\`" => TkXSTRING,
336 "/" => TkREGEXP,
337 "]" => TkDSTRING,
338 ":" => TkSYMBOL
339 }
340 DLtype2Token = {
341 "\"" => TkDSTRING,
342 "\`" => TkDXSTRING,
343 "/" => TkDREGEXP,
344 }
345
346 def lex_init()
347 @OP = IRB::SLex.new
348 @OP.def_rules("\0", "\004", "\032") do |op, io|
349 Token(TkEND_OF_SCRIPT)
350 end
351
352 @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io|
353 @space_seen = true
354 while getc =~ /[ \t\f\r\13]/; end
355 ungetc
356 Token(TkSPACE)
357 end
358
359 @OP.def_rule("#") do |op, io|
360 identify_comment
361 end
362
363 @OP.def_rule("=begin",
364 proc{|op, io| @prev_char_no == 0 && peek(0) =~ /\s/}) do
365 |op, io|
366 @ltype = "="
367 until getc == "\n"; end
368 until peek_equal?("=end") && peek(4) =~ /\s/
369 until getc == "\n"; end
370 end
371 gets
372 @ltype = nil
373 Token(TkRD_COMMENT)
374 end
375
376 @OP.def_rule("\n") do |op, io|
377 print "\\n\n" if RubyLex.debug?
378 case @lex_state
379 when EXPR_BEG, EXPR_FNAME, EXPR_DOT
380 @continue = true
381 else
382 @continue = false
383 @lex_state = EXPR_BEG
384 until (@indent_stack.empty? ||
385 [TkLPAREN, TkLBRACK, TkLBRACE,
386 TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
387 @indent_stack.pop
388 end
389 end
390 @here_header = false
391 @here_readed = []
392 Token(TkNL)
393 end
394
395 @OP.def_rules("*", "**",
396 "=", "==", "===",
397 "=~", "<=>",
398 "<", "<=",
399 ">", ">=", ">>",
400 "!", "!=", "!~") do
401 |op, io|
402 case @lex_state
403 when EXPR_FNAME, EXPR_DOT
404 @lex_state = EXPR_ARG
405 else
406 @lex_state = EXPR_BEG
407 end
408 Token(op)
409 end
410
411 @OP.def_rules("<<") do
412 |op, io|
413 tk = nil
414 if @lex_state != EXPR_END && @lex_state != EXPR_CLASS &&
415 (@lex_state != EXPR_ARG || @space_seen)
416 c = peek(0)
417 if /[-~"'`\w]/ =~ c
418 tk = identify_here_document
419 end
420 end
421 unless tk
422 tk = Token(op)
423 case @lex_state
424 when EXPR_FNAME, EXPR_DOT
425 @lex_state = EXPR_ARG
426 else
427 @lex_state = EXPR_BEG
428 end
429 end
430 tk
431 end
432
433 @OP.def_rules("'", '"') do
434 |op, io|
435 identify_string(op)
436 end
437
438 @OP.def_rules("`") do
439 |op, io|
440 if @lex_state == EXPR_FNAME
441 @lex_state = EXPR_END
442 Token(op)
443 else
444 identify_string(op)
445 end
446 end
447
448 @OP.def_rules('?') do
449 |op, io|
450 if @lex_state == EXPR_END
451 @lex_state = EXPR_BEG
452 Token(TkQUESTION)
453 else
454 ch = getc
455 if @lex_state == EXPR_ARG && ch =~ /\s/
456 ungetc
457 @lex_state = EXPR_BEG;
458 Token(TkQUESTION)
459 else
460 if (ch == '\\')
461 read_escape
462 end
463 @lex_state = EXPR_END
464 Token(TkINTEGER)
465 end
466 end
467 end
468
469 @OP.def_rules("&", "&&", "|", "||") do
470 |op, io|
471 @lex_state = EXPR_BEG
472 Token(op)
473 end
474
475 @OP.def_rules("+=", "-=", "*=", "**=",
476 "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do
477 |op, io|
478 @lex_state = EXPR_BEG
479 op =~ /^(.*)=$/
480 Token(TkOPASGN, $1)
481 end
482
483 @OP.def_rule("+@", proc{|op, io| @lex_state == EXPR_FNAME}) do
484 |op, io|
485 @lex_state = EXPR_ARG
486 Token(op)
487 end
488
489 @OP.def_rule("-@", proc{|op, io| @lex_state == EXPR_FNAME}) do
490 |op, io|
491 @lex_state = EXPR_ARG
492 Token(op)
493 end
494
495 @OP.def_rules("+", "-") do
496 |op, io|
497 catch(:RET) do
498 if @lex_state == EXPR_ARG
499 if @space_seen and peek(0) =~ /[0-9]/
500 throw :RET, identify_number
501 else
502 @lex_state = EXPR_BEG
503 end
504 elsif @lex_state != EXPR_END and peek(0) =~ /[0-9]/
505 throw :RET, identify_number
506 else
507 @lex_state = EXPR_BEG
508 end
509 Token(op)
510 end
511 end
512
513 @OP.def_rule(".") do
514 |op, io|
515 @lex_state = EXPR_BEG
516 if peek(0) =~ /[0-9]/
517 ungetc
518 identify_number
519 else
520 # for "obj.if" etc.
521 @lex_state = EXPR_DOT
522 Token(TkDOT)
523 end
524 end
525
526 @OP.def_rules("..", "...") do
527 |op, io|
528 @lex_state = EXPR_BEG
529 Token(op)
530 end
531
532 lex_int2
533 end
534
535 def lex_int2
536 @OP.def_rules("]", "}", ")") do
537 |op, io|
538 @lex_state = EXPR_END
539 @indent -= 1
540 @indent_stack.pop
541 Token(op)
542 end
543
544 @OP.def_rule(":") do
545 |op, io|
546 if @lex_state == EXPR_END || peek(0) =~ /\s/
547 @lex_state = EXPR_BEG
548 Token(TkCOLON)
549 else
550 @lex_state = EXPR_FNAME
551 Token(TkSYMBEG)
552 end
553 end
554
555 @OP.def_rule("::") do
556 |op, io|
557 if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen
558 @lex_state = EXPR_BEG
559 Token(TkCOLON3)
560 else
561 @lex_state = EXPR_DOT
562 Token(TkCOLON2)
563 end
564 end
565
566 @OP.def_rule("/") do
567 |op, io|
568 if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
569 identify_string(op)
570 elsif peek(0) == '='
571 getc
572 @lex_state = EXPR_BEG
573 Token(TkOPASGN, "/") #/)
574 elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
575 identify_string(op)
576 else
577 @lex_state = EXPR_BEG
578 Token("/") #/)
579 end
580 end
581
582 @OP.def_rules("^") do
583 |op, io|
584 @lex_state = EXPR_BEG
585 Token("^")
586 end
587
588 @OP.def_rules(",") do
589 |op, io|
590 @lex_state = EXPR_BEG
591 Token(op)
592 end
593
594 @OP.def_rules(";") do
595 |op, io|
596 @lex_state = EXPR_BEG
597 until (@indent_stack.empty? ||
598 [TkLPAREN, TkLBRACK, TkLBRACE,
599 TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
600 @indent_stack.pop
601 end
602 Token(op)
603 end
604
605 @OP.def_rule("~") do
606 |op, io|
607 @lex_state = EXPR_BEG
608 Token("~")
609 end
610
611 @OP.def_rule("~@", proc{|op, io| @lex_state == EXPR_FNAME}) do
612 |op, io|
613 @lex_state = EXPR_BEG
614 Token("~")
615 end
616
617 @OP.def_rule("(") do
618 |op, io|
619 @indent += 1
620 if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
621 @lex_state = EXPR_BEG
622 tk_c = TkfLPAREN
623 else
624 @lex_state = EXPR_BEG
625 tk_c = TkLPAREN
626 end
627 @indent_stack.push tk_c
628 Token(tk_c)
629 end
630
631 @OP.def_rule("[]", proc{|op, io| @lex_state == EXPR_FNAME}) do
632 |op, io|
633 @lex_state = EXPR_ARG
634 Token("[]")
635 end
636
637 @OP.def_rule("[]=", proc{|op, io| @lex_state == EXPR_FNAME}) do
638 |op, io|
639 @lex_state = EXPR_ARG
640 Token("[]=")
641 end
642
643 @OP.def_rule("[") do
644 |op, io|
645 @indent += 1
646 if @lex_state == EXPR_FNAME
647 tk_c = TkfLBRACK
648 else
649 if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
650 tk_c = TkLBRACK
651 elsif @lex_state == EXPR_ARG && @space_seen
652 tk_c = TkLBRACK
653 else
654 tk_c = TkfLBRACK
655 end
656 @lex_state = EXPR_BEG
657 end
658 @indent_stack.push tk_c
659 Token(tk_c)
660 end
661
662 @OP.def_rule("{") do
663 |op, io|
664 @indent += 1
665 if @lex_state != EXPR_END && @lex_state != EXPR_ARG
666 tk_c = TkLBRACE
667 else
668 tk_c = TkfLBRACE
669 end
670 @lex_state = EXPR_BEG
671 @indent_stack.push tk_c
672 Token(tk_c)
673 end
674
675 @OP.def_rule('\\') do
676 |op, io|
677 if getc == "\n"
678 @space_seen = true
679 @continue = true
680 Token(TkSPACE)
681 else
682 read_escape
683 Token("\\")
684 end
685 end
686
687 @OP.def_rule('%') do
688 |op, io|
689 if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
690 identify_quotation
691 elsif peek(0) == '='
692 getc
693 Token(TkOPASGN, :%)
694 elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
695 identify_quotation
696 else
697 @lex_state = EXPR_BEG
698 Token("%") #))
699 end
700 end
701
702 @OP.def_rule('$') do
703 |op, io|
704 identify_gvar
705 end
706
707 @OP.def_rule('@') do
708 |op, io|
709 if peek(0) =~ /[\w@]/
710 ungetc
711 identify_identifier
712 else
713 Token("@")
714 end
715 end
716
717 @OP.def_rule("") do
718 |op, io|
719 printf "MATCH: start %s: %s\n", op, io.inspect if RubyLex.debug?
720 if peek(0) =~ /[0-9]/
721 t = identify_number
722 elsif peek(0) =~ /[^\x00-\/:-@\[-^`{-\x7F]/
723 t = identify_identifier
724 end
725 printf "MATCH: end %s: %s\n", op, io.inspect if RubyLex.debug?
726 t
727 end
728
729 p @OP if RubyLex.debug?
730 end
731
732 def identify_gvar
733 @lex_state = EXPR_END
734
735 case ch = getc
736 when /[~_*$?!@\/\\;,=:<>".]/ #"
737 Token(TkGVAR, "$" + ch)
738 when "-"
739 Token(TkGVAR, "$-" + getc)
740 when "&", "`", "'", "+"
741 Token(TkBACK_REF, "$"+ch)
742 when /[1-9]/
743 while getc =~ /[0-9]/; end
744 ungetc
745 Token(TkNTH_REF)
746 when /\w/
747 ungetc
748 ungetc
749 identify_identifier
750 else
751 ungetc
752 Token("$")
753 end
754 end
755
756 def identify_identifier
757 token = ""
758 if peek(0) =~ /[$@]/
759 token.concat(c = getc)
760 if c == "@" and peek(0) == "@"
761 token.concat getc
762 end
763 end
764
765 while (ch = getc) =~ /[^\x00-\/:-@\[-^`{-\x7F]/
766 print ":", ch, ":" if RubyLex.debug?
767 token.concat ch
768 end
769 ungetc
770
771 if (ch == "!" || ch == "?") && token[0,1] =~ /\w/ && peek(0) != "="
772 token.concat getc
773 end
774
775 # almost fix token
776
777 case token
778 when /^\$/
779 return Token(TkGVAR, token)
780 when /^\@\@/
781 @lex_state = EXPR_END
782 # p Token(TkCVAR, token)
783 return Token(TkCVAR, token)
784 when /^\@/
785 @lex_state = EXPR_END
786 return Token(TkIVAR, token)
787 end
788
789 if @lex_state != EXPR_DOT
790 print token, "\n" if RubyLex.debug?
791
792 token_c, *trans = TkReading2Token[token]
793 if token_c
794 # reserved word?
795
796 if (@lex_state != EXPR_BEG &&
797 @lex_state != EXPR_FNAME &&
798 trans[1])
799 # modifiers
800 token_c = TkSymbol2Token[trans[1]]
801 @lex_state = trans[0]
802 else
803 if @lex_state != EXPR_FNAME and peek(0) != ':'
804 if ENINDENT_CLAUSE.include?(token)
805 # check for ``class = val'' etc.
806 valid = true
807 case token
808 when "class"
809 valid = false unless peek_match?(/^\s*(<<|\w|::)/)
810 when "def"
811 valid = false if peek_match?(/^\s*(([+\-\/*&\|^]|<<|>>|\|\||\&\&)=|\&\&|\|\|)/)
812 when "do"
813 valid = false if peek_match?(/^\s*([+\-\/*]?=|\*|<|>|\&)/)
814 when *ENINDENT_CLAUSE
815 valid = false if peek_match?(/^\s*([+\-\/*]?=|\*|<|>|\&|\|)/)
816 else
817 # no nothing
818 end
819 if valid
820 if token == "do"
821 if ![TkFOR, TkWHILE, TkUNTIL].include?(@indent_stack.last)
822 @indent += 1
823 @indent_stack.push token_c
824 end
825 else
826 @indent += 1
827 @indent_stack.push token_c
828 end
829 end
830
831 elsif DEINDENT_CLAUSE.include?(token)
832 @indent -= 1
833 @indent_stack.pop
834 end
835 @lex_state = trans[0]
836 else
837 @lex_state = EXPR_END
838 end
839 end
840 return Token(token_c, token)
841 end
842 end
843
844 if @lex_state == EXPR_FNAME
845 @lex_state = EXPR_END
846 if peek(0) == '='
847 token.concat getc
848 end
849 elsif @lex_state == EXPR_BEG || @lex_state == EXPR_DOT
850 @lex_state = EXPR_ARG
851 else
852 @lex_state = EXPR_END
853 end
854
855 if token[0, 1] =~ /[A-Z]/
856 return Token(TkCONSTANT, token)
857 elsif token[token.size - 1, 1] =~ /[!?]/
858 return Token(TkFID, token)
859 else
860 return Token(TkIDENTIFIER, token)
861 end
862 end
863
864 def identify_here_document
865 ch = getc
866 if ch == "-" || ch == "~"
867 ch = getc
868 indent = true
869 end
870 if /['"`]/ =~ ch
871 lt = ch
872 quoted = ""
873 while (c = getc) && c != lt
874 quoted.concat c
875 end
876 else
877 lt = '"'
878 quoted = ch.dup
879 while (c = getc) && c =~ /\w/
880 quoted.concat c
881 end
882 ungetc
883 end
884
885 ltback, @ltype = @ltype, lt
886 reserve = []
887 while ch = getc
888 reserve.push ch
889 if ch == "\\"
890 reserve.push ch = getc
891 elsif ch == "\n"
892 break
893 end
894 end
895
896 @here_header = false
897
898 line = ""
899 while ch = getc
900 if ch == "\n"
901 if line == quoted
902 break
903 end
904 line = ""
905 else
906 line.concat ch unless indent && line == "" && /\s/ =~ ch
907 if @ltype != "'" && ch == "#" && peek(0) == "{"
908 identify_string_dvar
909 end
910 end
911 end
912
913 @here_header = true
914 @here_readed.concat reserve
915 while ch = reserve.pop
916 ungetc ch
917 end
918
919 @ltype = ltback
920 @lex_state = EXPR_END
921 Token(Ltype2Token[lt])
922 end
923
924 def identify_quotation
925 ch = getc
926 if lt = PERCENT_LTYPE[ch]
927 ch = getc
928 elsif ch =~ /\W/
929 lt = "\""
930 else
931 RubyLex.fail SyntaxError, "unknown type of %string"
932 end
933 @quoted = ch unless @quoted = PERCENT_PAREN[ch]
934 identify_string(lt, @quoted)
935 end
936
937 def identify_number
938 @lex_state = EXPR_END
939
940 if peek(0) == "0" && peek(1) !~ /[.eE]/
941 getc
942 case peek(0)
943 when /[xX]/
944 ch = getc
945 match = /[0-9a-fA-F_]/
946 when /[bB]/
947 ch = getc
948 match = /[01_]/
949 when /[oO]/
950 ch = getc
951 match = /[0-7_]/
952 when /[dD]/
953 ch = getc
954 match = /[0-9_]/
955 when /[0-7]/
956 match = /[0-7_]/
957 when /[89]/
958 RubyLex.fail SyntaxError, "Invalid octal digit"
959 else
960 return Token(TkINTEGER)
961 end
962
963 len0 = true
964 non_digit = false
965 while ch = getc
966 if match =~ ch
967 if ch == "_"
968 if non_digit
969 RubyLex.fail SyntaxError, "trailing `#{ch}' in number"
970 else
971 non_digit = ch
972 end
973 else
974 non_digit = false
975 len0 = false
976 end
977 else
978 ungetc
979 if len0
980 RubyLex.fail SyntaxError, "numeric literal without digits"
981 end
982 if non_digit
983 RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
984 end
985 break
986 end
987 end
988 return Token(TkINTEGER)
989 end
990
991 type = TkINTEGER
992 allow_point = true
993 allow_e = true
994 non_digit = false
995 while ch = getc
996 case ch
997 when /[0-9]/
998 non_digit = false
999 when "_"
1000 non_digit = ch
1001 when allow_point && "."
1002 if non_digit
1003 RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
1004 end
1005 type = TkFLOAT
1006 if peek(0) !~ /[0-9]/
1007 type = TkINTEGER
1008 ungetc
1009 break
1010 end
1011 allow_point = false
1012 when allow_e && "e", allow_e && "E"
1013 if non_digit
1014 RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
1015 end
1016 type = TkFLOAT
1017 if peek(0) =~ /[+-]/
1018 getc
1019 end
1020 allow_e = false
1021 allow_point = false
1022 non_digit = ch
1023 else
1024 if non_digit
1025 RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
1026 end
1027 ungetc
1028 break
1029 end
1030 end
1031 Token(type)
1032 end
1033
1034 def identify_string(ltype, quoted = ltype)
1035 @ltype = ltype
1036 @quoted = quoted
1037 subtype = nil
1038 begin
1039 nest = 0
1040 while ch = getc
1041 if @quoted == ch and nest == 0
1042 break
1043 elsif @ltype != "'" && ch == "#" && peek(0) == "{"
1044 identify_string_dvar
1045 elsif @ltype != "'" && @ltype != "]" && @ltype != ":" and ch == "#"
1046 subtype = true
1047 elsif ch == '\\' and @ltype == "'" #'
1048 case ch = getc
1049 when "\\", "\n", "'"
1050 else
1051 ungetc
1052 end
1053 elsif ch == '\\' #'
1054 read_escape
1055 end
1056 if PERCENT_PAREN.values.include?(@quoted)
1057 if PERCENT_PAREN[ch] == @quoted
1058 nest += 1
1059 elsif ch == @quoted
1060 nest -= 1
1061 end
1062 end
1063 end
1064 if @ltype == "/"
1065 while /[imxoesun]/ =~ peek(0)
1066 getc
1067 end
1068 end
1069 if subtype
1070 Token(DLtype2Token[ltype])
1071 else
1072 Token(Ltype2Token[ltype])
1073 end
1074 ensure
1075 @ltype = nil
1076 @quoted = nil
1077 @lex_state = EXPR_END
1078 end
1079 end
1080
1081 def identify_string_dvar
1082 begin
1083 getc
1084
1085 reserve_continue = @continue
1086 reserve_ltype = @ltype
1087 reserve_indent = @indent
1088 reserve_indent_stack = @indent_stack
1089 reserve_state = @lex_state
1090 reserve_quoted = @quoted
1091
1092 @ltype = nil
1093 @quoted = nil
1094 @indent = 0
1095 @indent_stack = []
1096 @lex_state = EXPR_BEG
1097
1098 loop do
1099 @continue = false
1100 prompt
1101 tk = token
1102 if @ltype or @continue or @indent >= 0
1103 next
1104 end
1105 break if tk.kind_of?(TkRBRACE)
1106 end
1107 ensure
1108 @continue = reserve_continue
1109 @ltype = reserve_ltype
1110 @indent = reserve_indent
1111 @indent_stack = reserve_indent_stack
1112 @lex_state = reserve_state
1113 @quoted = reserve_quoted
1114 end
1115 end
1116
1117 def identify_comment
1118 @ltype = "#"
1119
1120 while ch = getc
1121 if ch == "\n"
1122 @ltype = nil
1123 ungetc
1124 break
1125 end
1126 end
1127 return Token(TkCOMMENT)
1128 end
1129
1130 def read_escape
1131 case ch = getc
1132 when "\n", "\r", "\f"
1133 when "\\", "n", "t", "r", "f", "v", "a", "e", "b", "s" #"
1134 when /[0-7]/
1135 ungetc ch
1136 3.times do
1137 case ch = getc
1138 when /[0-7]/
1139 when nil
1140 break
1141 else
1142 ungetc
1143 break
1144 end
1145 end
1146
1147 when "x"
1148 2.times do
1149 case ch = getc
1150 when /[0-9a-fA-F]/
1151 when nil
1152 break
1153 else
1154 ungetc
1155 break
1156 end
1157 end
1158
1159 when "M"
1160 if (ch = getc) != '-'
1161 ungetc
1162 else
1163 if (ch = getc) == "\\" #"
1164 read_escape
1165 end
1166 end
1167
1168 when "C", "c" #, "^"
1169 if ch == "C" and (ch = getc) != "-"
1170 ungetc
1171 elsif (ch = getc) == "\\" #"
1172 read_escape
1173 end
1174 else
1175 # other characters
1176 end
1177 end
1178 end
1179 # :startdoc:
+0
-267
vendor/irb/ruby-token.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/ruby-token.rb - ruby tokens
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 # :stopdoc:
12 module RubyToken
13 EXPR_BEG = :EXPR_BEG
14 EXPR_MID = :EXPR_MID
15 EXPR_END = :EXPR_END
16 EXPR_ARG = :EXPR_ARG
17 EXPR_FNAME = :EXPR_FNAME
18 EXPR_DOT = :EXPR_DOT
19 EXPR_CLASS = :EXPR_CLASS
20
21 class Token
22 def initialize(seek, line_no, char_no)
23 @seek = seek
24 @line_no = line_no
25 @char_no = char_no
26 end
27 attr_reader :seek, :line_no, :char_no
28 end
29
30 class TkNode < Token
31 def initialize(seek, line_no, char_no)
32 super
33 end
34 attr_reader :node
35 end
36
37 class TkId < Token
38 def initialize(seek, line_no, char_no, name)
39 super(seek, line_no, char_no)
40 @name = name
41 end
42 attr_reader :name
43 end
44
45 class TkVal < Token
46 def initialize(seek, line_no, char_no, value = nil)
47 super(seek, line_no, char_no)
48 @value = value
49 end
50 attr_reader :value
51 end
52
53 class TkOp < Token
54 attr_accessor :name
55 end
56
57 class TkOPASGN < TkOp
58 def initialize(seek, line_no, char_no, op)
59 super(seek, line_no, char_no)
60 op = TkReading2Token[op][0] unless op.kind_of?(Symbol)
61 @op = op
62 end
63 attr_reader :op
64 end
65
66 class TkUnknownChar < Token
67 def initialize(seek, line_no, char_no, id)
68 super(seek, line_no, char_no)
69 @name = name
70 end
71 attr_reader :name
72 end
73
74 class TkError < Token
75 end
76
77 def Token(token, value = nil)
78 case token
79 when String
80 if (tk = TkReading2Token[token]).nil?
81 IRB.fail TkReading2TokenNoKey, token
82 end
83 tk = Token(tk[0], value)
84 if tk.kind_of?(TkOp)
85 tk.name = token
86 end
87 return tk
88 when Symbol
89 if (tk = TkSymbol2Token[token]).nil?
90 IRB.fail TkSymbol2TokenNoKey, token
91 end
92 return Token(tk[0], value)
93 else
94 if (token.ancestors & [TkId, TkVal, TkOPASGN, TkUnknownChar]).empty?
95 token.new(@prev_seek, @prev_line_no, @prev_char_no)
96 else
97 token.new(@prev_seek, @prev_line_no, @prev_char_no, value)
98 end
99 end
100 end
101
102 TokenDefinitions = [
103 [:TkCLASS, TkId, "class", EXPR_CLASS],
104 [:TkMODULE, TkId, "module", EXPR_BEG],
105 [:TkDEF, TkId, "def", EXPR_FNAME],
106 [:TkUNDEF, TkId, "undef", EXPR_FNAME],
107 [:TkBEGIN, TkId, "begin", EXPR_BEG],
108 [:TkRESCUE, TkId, "rescue", EXPR_MID],
109 [:TkENSURE, TkId, "ensure", EXPR_BEG],
110 [:TkEND, TkId, "end", EXPR_END],
111 [:TkIF, TkId, "if", EXPR_BEG, :TkIF_MOD],
112 [:TkUNLESS, TkId, "unless", EXPR_BEG, :TkUNLESS_MOD],
113 [:TkTHEN, TkId, "then", EXPR_BEG],
114 [:TkELSIF, TkId, "elsif", EXPR_BEG],
115 [:TkELSE, TkId, "else", EXPR_BEG],
116 [:TkCASE, TkId, "case", EXPR_BEG],
117 [:TkWHEN, TkId, "when", EXPR_BEG],
118 [:TkWHILE, TkId, "while", EXPR_BEG, :TkWHILE_MOD],
119 [:TkUNTIL, TkId, "until", EXPR_BEG, :TkUNTIL_MOD],
120 [:TkFOR, TkId, "for", EXPR_BEG],
121 [:TkBREAK, TkId, "break", EXPR_END],
122 [:TkNEXT, TkId, "next", EXPR_END],
123 [:TkREDO, TkId, "redo", EXPR_END],
124 [:TkRETRY, TkId, "retry", EXPR_END],
125 [:TkIN, TkId, "in", EXPR_BEG],
126 [:TkDO, TkId, "do", EXPR_BEG],
127 [:TkRETURN, TkId, "return", EXPR_MID],
128 [:TkYIELD, TkId, "yield", EXPR_END],
129 [:TkSUPER, TkId, "super", EXPR_END],
130 [:TkSELF, TkId, "self", EXPR_END],
131 [:TkNIL, TkId, "nil", EXPR_END],
132 [:TkTRUE, TkId, "true", EXPR_END],
133 [:TkFALSE, TkId, "false", EXPR_END],
134 [:TkAND, TkId, "and", EXPR_BEG],
135 [:TkOR, TkId, "or", EXPR_BEG],
136 [:TkNOT, TkId, "not", EXPR_BEG],
137 [:TkIF_MOD, TkId],
138 [:TkUNLESS_MOD, TkId],
139 [:TkWHILE_MOD, TkId],
140 [:TkUNTIL_MOD, TkId],
141 [:TkALIAS, TkId, "alias", EXPR_FNAME],
142 [:TkDEFINED, TkId, "defined?", EXPR_END],
143 [:TklBEGIN, TkId, "BEGIN", EXPR_END],
144 [:TklEND, TkId, "END", EXPR_END],
145 [:Tk__LINE__, TkId, "__LINE__", EXPR_END],
146 [:Tk__FILE__, TkId, "__FILE__", EXPR_END],
147
148 [:TkIDENTIFIER, TkId],
149 [:TkFID, TkId],
150 [:TkGVAR, TkId],
151 [:TkCVAR, TkId],
152 [:TkIVAR, TkId],
153 [:TkCONSTANT, TkId],
154
155 [:TkINTEGER, TkVal],
156 [:TkFLOAT, TkVal],
157 [:TkSTRING, TkVal],
158 [:TkXSTRING, TkVal],
159 [:TkREGEXP, TkVal],
160 [:TkSYMBOL, TkVal],
161
162 [:TkDSTRING, TkNode],
163 [:TkDXSTRING, TkNode],
164 [:TkDREGEXP, TkNode],
165 [:TkNTH_REF, TkNode],
166 [:TkBACK_REF, TkNode],
167
168 [:TkUPLUS, TkOp, "+@"],
169 [:TkUMINUS, TkOp, "-@"],
170 [:TkPOW, TkOp, "**"],
171 [:TkCMP, TkOp, "<=>"],
172 [:TkEQ, TkOp, "=="],
173 [:TkEQQ, TkOp, "==="],
174 [:TkNEQ, TkOp, "!="],
175 [:TkGEQ, TkOp, ">="],
176 [:TkLEQ, TkOp, "<="],
177 [:TkANDOP, TkOp, "&&"],
178 [:TkOROP, TkOp, "||"],
179 [:TkMATCH, TkOp, "=~"],
180 [:TkNMATCH, TkOp, "!~"],
181 [:TkDOT2, TkOp, ".."],
182 [:TkDOT3, TkOp, "..."],
183 [:TkAREF, TkOp, "[]"],
184 [:TkASET, TkOp, "[]="],
185 [:TkLSHFT, TkOp, "<<"],
186 [:TkRSHFT, TkOp, ">>"],
187 [:TkCOLON2, TkOp],
188 [:TkCOLON3, TkOp],
189 [:TkASSOC, TkOp, "=>"],
190 [:TkQUESTION, TkOp, "?"], #?
191 [:TkCOLON, TkOp, ":"], #:
192
193 [:TkfLPAREN], # func( #
194 [:TkfLBRACK], # func[ #
195 [:TkfLBRACE], # func{ #
196 [:TkSTAR], # *arg
197 [:TkAMPER], # &arg #
198 [:TkSYMBEG], # :SYMBOL
199
200 [:TkGT, TkOp, ">"],
201 [:TkLT, TkOp, "<"],
202 [:TkPLUS, TkOp, "+"],
203 [:TkMINUS, TkOp, "-"],
204 [:TkMULT, TkOp, "*"],
205 [:TkDIV, TkOp, "/"],
206 [:TkMOD, TkOp, "%"],
207 [:TkBITOR, TkOp, "|"],
208 [:TkBITXOR, TkOp, "^"],
209 [:TkBITAND, TkOp, "&"],
210 [:TkBITNOT, TkOp, "~"],
211 [:TkNOTOP, TkOp, "!"],
212
213 [:TkBACKQUOTE, TkOp, "`"],
214
215 [:TkASSIGN, Token, "="],
216 [:TkDOT, Token, "."],
217 [:TkLPAREN, Token, "("], #(exp)
218 [:TkLBRACK, Token, "["], #[arry]
219 [:TkLBRACE, Token, "{"], #{hash}
220 [:TkRPAREN, Token, ")"],
221 [:TkRBRACK, Token, "]"],
222 [:TkRBRACE, Token, "}"],
223 [:TkCOMMA, Token, ","],
224 [:TkSEMICOLON, Token, ";"],
225
226 [:TkCOMMENT],
227 [:TkRD_COMMENT],
228 [:TkSPACE],
229 [:TkNL],
230 [:TkEND_OF_SCRIPT],
231
232 [:TkBACKSLASH, TkUnknownChar, "\\"],
233 [:TkAT, TkUnknownChar, "@"],
234 [:TkDOLLAR, TkUnknownChar, "$"],
235 ]
236
237 # {reading => token_class}
238 # {reading => [token_class, *opt]}
239 TkReading2Token = {}
240 TkSymbol2Token = {}
241
242 def RubyToken.def_token(token_n, super_token = Token, reading = nil, *opts)
243 token_n = token_n.id2name if token_n.kind_of?(Symbol)
244 if RubyToken.const_defined?(token_n)
245 IRB.fail AlreadyDefinedToken, token_n
246 end
247 token_c = eval("class #{token_n} < #{super_token}; end; #{token_n}")
248
249 if reading
250 if TkReading2Token[reading]
251 IRB.fail TkReading2TokenDuplicateError, token_n, reading
252 end
253 if opts.empty?
254 TkReading2Token[reading] = [token_c]
255 else
256 TkReading2Token[reading] = [token_c].concat(opts)
257 end
258 end
259 TkSymbol2Token[token_n.intern] = token_c
260 end
261
262 for defs in TokenDefinitions
263 def_token(*defs)
264 end
265 end
266 # :startdoc:
+0
-282
vendor/irb/slex.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/slex.rb - simple lex analyzer
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 require "e2mmap"
13 require_relative "notifier"
14
15 # :stopdoc:
16 module IRB
17 class SLex
18
19 extend Exception2MessageMapper
20 def_exception :ErrNodeNothing, "node nothing"
21 def_exception :ErrNodeAlreadyExists, "node already exists"
22
23 DOUT = Notifier::def_notifier("SLex::")
24 D_WARN = DOUT::def_notifier(1, "Warn: ")
25 D_DEBUG = DOUT::def_notifier(2, "Debug: ")
26 D_DETAIL = DOUT::def_notifier(4, "Detail: ")
27
28 DOUT.level = Notifier::D_NOMSG
29
30 def initialize
31 @head = Node.new("")
32 end
33
34 def def_rule(token, preproc = nil, postproc = nil, &block)
35 D_DETAIL.pp token
36
37 postproc = block if block_given?
38 create(token, preproc, postproc)
39 end
40
41 def def_rules(*tokens, &block)
42 if block_given?
43 p = block
44 end
45 for token in tokens
46 def_rule(token, nil, p)
47 end
48 end
49
50 def preproc(token, proc)
51 node = search(token)
52 node.preproc=proc
53 end
54
55 # need a check?
56 def postproc(token)
57 node = search(token, proc)
58 node.postproc=proc
59 end
60
61 def search(token)
62 @head.search(token.split(//))
63 end
64
65 def create(token, preproc = nil, postproc = nil)
66 @head.create_subnode(token.split(//), preproc, postproc)
67 end
68
69 def match(token)
70 case token
71 when Array
72 when String
73 return match(token.split(//))
74 else
75 return @head.match_io(token)
76 end
77 ret = @head.match(token)
78 D_DETAIL.exec_if{D_DETAIL.printf "match end: %s:%s\n", ret, token.inspect}
79 ret
80 end
81
82 def inspect
83 format("<SLex: @head = %s>", @head.inspect)
84 end
85
86 #----------------------------------------------------------------------
87 #
88 # class Node -
89 #
90 #----------------------------------------------------------------------
91 class Node
92 # if postproc is nil, this node is an abstract node.
93 # if postproc is non-nil, this node is a real node.
94 def initialize(preproc = nil, postproc = nil)
95 @Tree = {}
96 @preproc = preproc
97 @postproc = postproc
98 end
99
100 attr_accessor :preproc
101 attr_accessor :postproc
102
103 def search(chrs, opt = nil)
104 return self if chrs.empty?
105 ch = chrs.shift
106 if node = @Tree[ch]
107 node.search(chrs, opt)
108 else
109 if opt
110 chrs.unshift ch
111 self.create_subnode(chrs)
112 else
113 SLex.fail ErrNodeNothing
114 end
115 end
116 end
117
118 def create_subnode(chrs, preproc = nil, postproc = nil)
119 if chrs.empty?
120 if @postproc
121 D_DETAIL.pp node
122 SLex.fail ErrNodeAlreadyExists
123 else
124 D_DEBUG.puts "change abstract node to real node."
125 @preproc = preproc
126 @postproc = postproc
127 end
128 return self
129 end
130
131 ch = chrs.shift
132 if node = @Tree[ch]
133 if chrs.empty?
134 if node.postproc
135 DebugLogger.pp node
136 DebugLogger.pp self
137 DebugLogger.pp ch
138 DebugLogger.pp chrs
139 SLex.fail ErrNodeAlreadyExists
140 else
141 D_WARN.puts "change abstract node to real node"
142 node.preproc = preproc
143 node.postproc = postproc
144 end
145 else
146 node.create_subnode(chrs, preproc, postproc)
147 end
148 else
149 if chrs.empty?
150 node = Node.new(preproc, postproc)
151 else
152 node = Node.new
153 node.create_subnode(chrs, preproc, postproc)
154 end
155 @Tree[ch] = node
156 end
157 node
158 end
159
160 #
161 # chrs: String
162 # character array
163 # io must have getc()/ungetc(); and ungetc() must be
164 # able to be called arbitrary number of times.
165 #
166 def match(chrs, op = "")
167 D_DETAIL.print "match>: ", chrs, "op:", op, "\n"
168 if chrs.empty?
169 if @preproc.nil? || @preproc.call(op, chrs)
170 DOUT.printf(D_DETAIL, "op1: %s\n", op)
171 @postproc.call(op, chrs)
172 else
173 nil
174 end
175 else
176 ch = chrs.shift
177 if node = @Tree[ch]
178 if ret = node.match(chrs, op+ch)
179 return ret
180 else
181 chrs.unshift ch
182 if @postproc and @preproc.nil? || @preproc.call(op, chrs)
183 DOUT.printf(D_DETAIL, "op2: %s\n", op.inspect)
184 ret = @postproc.call(op, chrs)
185 return ret
186 else
187 return nil
188 end
189 end
190 else
191 chrs.unshift ch
192 if @postproc and @preproc.nil? || @preproc.call(op, chrs)
193 DOUT.printf(D_DETAIL, "op3: %s\n", op)
194 @postproc.call(op, chrs)
195 return ""
196 else
197 return nil
198 end
199 end
200 end
201 end
202
203 def match_io(io, op = "")
204 if op == ""
205 ch = io.getc
206 if ch == nil
207 return nil
208 end
209 else
210 ch = io.getc_of_rests
211 end
212 if ch.nil?
213 if @preproc.nil? || @preproc.call(op, io)
214 D_DETAIL.printf("op1: %s\n", op)
215 @postproc.call(op, io)
216 else
217 nil
218 end
219 else
220 if node = @Tree[ch]
221 if ret = node.match_io(io, op+ch)
222 ret
223 else
224 io.ungetc ch
225 if @postproc and @preproc.nil? || @preproc.call(op, io)
226 DOUT.exec_if{D_DETAIL.printf "op2: %s\n", op.inspect}
227 @postproc.call(op, io)
228 else
229 nil
230 end
231 end
232 else
233 io.ungetc ch
234 if @postproc and @preproc.nil? || @preproc.call(op, io)
235 D_DETAIL.printf("op3: %s\n", op)
236 @postproc.call(op, io)
237 else
238 nil
239 end
240 end
241 end
242 end
243 end
244 end
245 end
246 # :startdoc:
247
248 if $0 == __FILE__
249 case $1
250 when "1"
251 tr = SLex.new
252 print "0: ", tr.inspect, "\n"
253 tr.def_rule("=") {print "=\n"}
254 print "1: ", tr.inspect, "\n"
255 tr.def_rule("==") {print "==\n"}
256 print "2: ", tr.inspect, "\n"
257
258 print "case 1:\n"
259 print tr.match("="), "\n"
260 print "case 2:\n"
261 print tr.match("=="), "\n"
262 print "case 3:\n"
263 print tr.match("=>"), "\n"
264
265 when "2"
266 tr = SLex.new
267 print "0: ", tr.inspect, "\n"
268 tr.def_rule("=") {print "=\n"}
269 print "1: ", tr.inspect, "\n"
270 tr.def_rule("==", proc{false}) {print "==\n"}
271 print "2: ", tr.inspect, "\n"
272
273 print "case 1:\n"
274 print tr.match("="), "\n"
275 print "case 2:\n"
276 print tr.match("=="), "\n"
277 print "case 3:\n"
278 print tr.match("=>"), "\n"
279 end
280 exit
281 end
+0
-7
vendor/irb/src_encoding.rb less more
0 # frozen_string_literal: false
1 # DO NOT WRITE ANY MAGIC COMMENT HERE.
2 module IRB
3 def self.default_src_encoding
4 return __ENCODING__
5 end
6 end
+0
-17
vendor/irb/version.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/version.rb - irb version definition file
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ishitsuka.com)
6 #
7 # --
8 #
9 #
10 #
11
12 module IRB # :nodoc:
13 VERSION = "1.0.0"
14 @RELEASE_VERSION = VERSION
15 @LAST_UPDATE_DATE = "2018-12-18"
16 end
+0
-143
vendor/irb/workspace.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/workspace-binding.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 module IRB # :nodoc:
12 class WorkSpace
13 # Creates a new workspace.
14 #
15 # set self to main if specified, otherwise
16 # inherit main from TOPLEVEL_BINDING.
17 def initialize(*main)
18 if main[0].kind_of?(Binding)
19 @binding = main.shift
20 elsif IRB.conf[:SINGLE_IRB]
21 @binding = TOPLEVEL_BINDING
22 else
23 case IRB.conf[:CONTEXT_MODE]
24 when 0 # binding in proc on TOPLEVEL_BINDING
25 @binding = eval("proc{binding}.call",
26 TOPLEVEL_BINDING,
27 __FILE__,
28 __LINE__)
29 when 1 # binding in loaded file
30 require "tempfile"
31 f = Tempfile.open("irb-binding")
32 f.print <<EOF
33 $binding = binding
34 EOF
35 f.close
36 load f.path
37 @binding = $binding
38
39 when 2 # binding in loaded file(thread use)
40 unless defined? BINDING_QUEUE
41 IRB.const_set(:BINDING_QUEUE, Thread::SizedQueue.new(1))
42 Thread.abort_on_exception = true
43 Thread.start do
44 eval "require \"irb/ws-for-case-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__
45 end
46 Thread.pass
47 end
48 @binding = BINDING_QUEUE.pop
49
50 when 3 # binding in function on TOPLEVEL_BINDING(default)
51 @binding = eval("def irb_binding; private; binding; end; irb_binding",
52 TOPLEVEL_BINDING,
53 __FILE__,
54 __LINE__ - 3)
55 end
56 end
57 if main.empty?
58 @main = eval("self", @binding)
59 else
60 @main = main[0]
61 IRB.conf[:__MAIN__] = @main
62 case @main
63 when Module
64 @binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
65 else
66 begin
67 @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
68 rescue TypeError
69 IRB.fail CantChangeBinding, @main.inspect
70 end
71 end
72 end
73 @binding.local_variable_set(:_, nil)
74 end
75
76 # The Binding of this workspace
77 attr_reader :binding
78 # The top-level workspace of this context, also available as
79 # <code>IRB.conf[:__MAIN__]</code>
80 attr_reader :main
81
82 # Evaluate the given +statements+ within the context of this workspace.
83 def evaluate(context, statements, file = __FILE__, line = __LINE__)
84 eval(statements, @binding, file, line)
85 end
86
87 def local_variable_set(name, value)
88 @binding.local_variable_set(name, value)
89 end
90
91 def local_variable_get(name)
92 @binding.local_variable_get(name)
93 end
94
95 # error message manipulator
96 def filter_backtrace(bt)
97 case IRB.conf[:CONTEXT_MODE]
98 when 0
99 return nil if bt =~ /\(irb_local_binding\)/
100 when 1
101 if(bt =~ %r!/tmp/irb-binding! or
102 bt =~ %r!irb/.*\.rb! or
103 bt =~ /irb\.rb/)
104 return nil
105 end
106 when 2
107 return nil if bt =~ /irb\/.*\.rb/
108 return nil if bt =~ /irb\.rb/
109 when 3
110 return nil if bt =~ /irb\/.*\.rb/
111 return nil if bt =~ /irb\.rb/
112 bt = bt.sub(/:\s*in `irb_binding'/, '')
113 end
114 bt
115 end
116
117 def code_around_binding
118 file, pos = @binding.source_location
119
120 unless defined?(::SCRIPT_LINES__[file]) && lines = ::SCRIPT_LINES__[file]
121 begin
122 lines = File.readlines(file)
123 rescue SystemCallError
124 return
125 end
126 end
127 pos -= 1
128
129 start_pos = [pos - 5, 0].max
130 end_pos = [pos + 5, lines.size - 1].min
131
132 fmt = " %2s %#{end_pos.to_s.length}d: %s"
133 body = (start_pos..end_pos).map do |current_pos|
134 sprintf(fmt, pos == current_pos ? '=>' : '', current_pos + 1, lines[current_pos])
135 end.join("")
136 "\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}\n"
137 end
138
139 def IRB.delete_caller
140 end
141 end
142 end
+0
-15
vendor/irb/ws-for-case-2.rb less more
0 # frozen_string_literal: false
1 #
2 # irb/ws-for-case-2.rb -
3 # $Release Version: 0.9.6$
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11
12 while true
13 IRB::BINDING_QUEUE.push _ = binding
14 end
+0
-170
vendor/irb/xmp.rb less more
0 # frozen_string_literal: false
1 #
2 # xmp.rb - irb version of gotoken xmp
3 # $Release Version: 0.9$
4 # $Revision$
5 # by Keiju ISHITSUKA(Nippon Rational Inc.)
6 #
7 # --
8 #
9 #
10 #
11
12 require "irb"
13 require_relative "frame"
14
15 # An example printer for irb.
16 #
17 # It's much like the standard library PrettyPrint, that shows the value of each
18 # expression as it runs.
19 #
20 # In order to use this library, you must first require it:
21 #
22 # require 'irb/xmp'
23 #
24 # Now, you can take advantage of the Object#xmp convenience method.
25 #
26 # xmp <<END
27 # foo = "bar"
28 # baz = 42
29 # END
30 # #=> foo = "bar"
31 # #==>"bar"
32 # #=> baz = 42
33 # #==>42
34 #
35 # You can also create an XMP object, with an optional binding to print
36 # expressions in the given binding:
37 #
38 # ctx = binding
39 # x = XMP.new ctx
40 # x.puts
41 # #=> today = "a good day"
42 # #==>"a good day"
43 # ctx.eval 'today # is what?'
44 # #=> "a good day"
45 class XMP
46
47 # Creates a new XMP object.
48 #
49 # The top-level binding or, optional +bind+ parameter will be used when
50 # creating the workspace. See WorkSpace.new for more information.
51 #
52 # This uses the +:XMP+ prompt mode, see IRB@Customizing+the+IRB+Prompt for
53 # full detail.
54 def initialize(bind = nil)
55 IRB.init_config(nil)
56
57 IRB.conf[:PROMPT_MODE] = :XMP
58
59 bind = IRB::Frame.top(1) unless bind
60 ws = IRB::WorkSpace.new(bind)
61 @io = StringInputMethod.new
62 @irb = IRB::Irb.new(ws, @io)
63 @irb.context.ignore_sigint = false
64
65 IRB.conf[:MAIN_CONTEXT] = @irb.context
66 end
67
68 # Evaluates the given +exps+, for example:
69 #
70 # require 'irb/xmp'
71 # x = XMP.new
72 #
73 # x.puts '{:a => 1, :b => 2, :c => 3}'
74 # #=> {:a => 1, :b => 2, :c => 3}
75 # # ==>{:a=>1, :b=>2, :c=>3}
76 # x.puts 'foo = "bar"'
77 # # => foo = "bar"
78 # # ==>"bar"
79 def puts(exps)
80 @io.puts exps
81
82 if @irb.context.ignore_sigint
83 begin
84 trap_proc_b = trap("SIGINT"){@irb.signal_handle}
85 catch(:IRB_EXIT) do
86 @irb.eval_input
87 end
88 ensure
89 trap("SIGINT", trap_proc_b)
90 end
91 else
92 catch(:IRB_EXIT) do
93 @irb.eval_input
94 end
95 end
96 end
97
98 # A custom InputMethod class used by XMP for evaluating string io.
99 class StringInputMethod < IRB::InputMethod
100 # Creates a new StringInputMethod object
101 def initialize
102 super
103 @exps = []
104 end
105
106 # Whether there are any expressions left in this printer.
107 def eof?
108 @exps.empty?
109 end
110
111 # Reads the next expression from this printer.
112 #
113 # See IO#gets for more information.
114 def gets
115 while l = @exps.shift
116 next if /^\s+$/ =~ l
117 l.concat "\n"
118 print @prompt, l
119 break
120 end
121 l
122 end
123
124 # Concatenates all expressions in this printer, separated by newlines.
125 #
126 # An Encoding::CompatibilityError is raised of the given +exps+'s encoding
127 # doesn't match the previous expression evaluated.
128 def puts(exps)
129 if @encoding and exps.encoding != @encoding
130 enc = Encoding.compatible?(@exps.join("\n"), exps)
131 if enc.nil?
132 raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one"
133 else
134 @encoding = enc
135 end
136 else
137 @encoding = exps.encoding
138 end
139 @exps.concat exps.split(/\n/)
140 end
141
142 # Returns the encoding of last expression printed by #puts.
143 attr_reader :encoding
144 end
145 end
146
147 # A convenience method that's only available when the you require the IRB::XMP standard library.
148 #
149 # Creates a new XMP object, using the given expressions as the +exps+
150 # parameter, and optional binding as +bind+ or uses the top-level binding. Then
151 # evaluates the given expressions using the +:XMP+ prompt mode.
152 #
153 # For example:
154 #
155 # require 'irb/xmp'
156 # ctx = binding
157 # xmp 'foo = "bar"', ctx
158 # #=> foo = "bar"
159 # #==>"bar"
160 # ctx.eval 'foo'
161 # #=> "bar"
162 #
163 # See XMP.new for more information.
164 def xmp(exps, bind = nil)
165 bind = IRB::Frame.top(1) unless bind
166 xmp = XMP.new(bind)
167 xmp.puts exps
168 xmp
169 end
+0
-798
vendor/irb.rb less more
0 # frozen_string_literal: false
1 #
2 # irb.rb - irb main module
3 # $Release Version: 0.9.6 $
4 # $Revision$
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
6 #
7 # --
8 #
9 #
10 #
11 require "e2mmap"
12
13 require "irb/init"
14 require "irb/context"
15 require "irb/extend-command"
16
17 require "irb/ruby-lex"
18 require "irb/input-method"
19 require "irb/locale"
20
21 require "irb/version"
22
23 # IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby
24 # expressions read from the standard input.
25 #
26 # The +irb+ command from your shell will start the interpreter.
27 #
28 # == Usage
29 #
30 # Use of irb is easy if you know Ruby.
31 #
32 # When executing irb, prompts are displayed as follows. Then, enter the Ruby
33 # expression. An input is executed when it is syntactically complete.
34 #
35 # $ irb
36 # irb(main):001:0> 1+2
37 # #=> 3
38 # irb(main):002:0> class Foo
39 # irb(main):003:1> def foo
40 # irb(main):004:2> print 1
41 # irb(main):005:2> end
42 # irb(main):006:1> end
43 # #=> nil
44 #
45 # The Readline extension module can be used with irb. Use of Readline is
46 # default if it's installed.
47 #
48 # == Command line options
49 #
50 # Usage: irb.rb [options] [programfile] [arguments]
51 # -f Suppress read of ~/.irbrc
52 # -d Set $DEBUG to true (same as `ruby -d')
53 # -r load-module Same as `ruby -r'
54 # -I path Specify $LOAD_PATH directory
55 # -U Same as `ruby -U`
56 # -E enc Same as `ruby -E`
57 # -w Same as `ruby -w`
58 # -W[level=2] Same as `ruby -W`
59 # --inspect Use `inspect' for output (default except for bc mode)
60 # --noinspect Don't use inspect for output
61 # --readline Use Readline extension module
62 # --noreadline Don't use Readline extension module
63 # --prompt prompt-mode
64 # --prompt-mode prompt-mode
65 # Switch prompt mode. Pre-defined prompt modes are
66 # `default', `simple', `xmp' and `inf-ruby'
67 # --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
68 # Suppresses --readline.
69 # --simple-prompt Simple prompt mode
70 # --noprompt No prompt mode
71 # --tracer Display trace for each execution of commands.
72 # --back-trace-limit n
73 # Display backtrace top n and tail n. The default
74 # value is 16.
75 # --irb_debug n Set internal debug level to n (not for popular use)
76 # -v, --version Print the version of irb
77 #
78 # == Configuration
79 #
80 # IRB reads from <code>~/.irbrc</code> when it's invoked.
81 #
82 # If <code>~/.irbrc</code> doesn't exist, +irb+ will try to read in the following order:
83 #
84 # * +.irbrc+
85 # * +irb.rc+
86 # * +_irbrc+
87 # * <code>$irbrc</code>
88 #
89 # The following are alternatives to the command line options. To use them type
90 # as follows in an +irb+ session:
91 #
92 # IRB.conf[:IRB_NAME]="irb"
93 # IRB.conf[:INSPECT_MODE]=nil
94 # IRB.conf[:IRB_RC] = nil
95 # IRB.conf[:BACK_TRACE_LIMIT]=16
96 # IRB.conf[:USE_LOADER] = false
97 # IRB.conf[:USE_READLINE] = nil
98 # IRB.conf[:USE_TRACER] = false
99 # IRB.conf[:IGNORE_SIGINT] = true
100 # IRB.conf[:IGNORE_EOF] = false
101 # IRB.conf[:PROMPT_MODE] = :DEFAULT
102 # IRB.conf[:PROMPT] = {...}
103 # IRB.conf[:DEBUG_LEVEL]=0
104 #
105 # === Auto indentation
106 #
107 # To enable auto-indent mode in irb, add the following to your +.irbrc+:
108 #
109 # IRB.conf[:AUTO_INDENT] = true
110 #
111 # === Autocompletion
112 #
113 # To enable autocompletion for irb, add the following to your +.irbrc+:
114 #
115 # require 'irb/completion'
116 #
117 # === History
118 #
119 # By default, irb disables history and will not store any commands you used.
120 #
121 # If you want to enable history, add the following to your +.irbrc+:
122 #
123 # IRB.conf[:SAVE_HISTORY] = 1000
124 #
125 # This will now store the last 1000 commands in <code>~/.irb_history</code>.
126 #
127 # See IRB::Context#save_history= for more information.
128 #
129 # == Customizing the IRB Prompt
130 #
131 # In order to customize the prompt, you can change the following Hash:
132 #
133 # IRB.conf[:PROMPT]
134 #
135 # This example can be used in your +.irbrc+
136 #
137 # IRB.conf[:PROMPT][:MY_PROMPT] = { # name of prompt mode
138 # :AUTO_INDENT => true, # enables auto-indent mode
139 # :PROMPT_I => ">> ", # simple prompt
140 # :PROMPT_S => nil, # prompt for continuated strings
141 # :PROMPT_C => nil, # prompt for continuated statement
142 # :RETURN => " ==>%s\n" # format to return value
143 # }
144 #
145 # IRB.conf[:PROMPT_MODE] = :MY_PROMPT
146 #
147 # Or, invoke irb with the above prompt mode by:
148 #
149 # irb --prompt my-prompt
150 #
151 # Constants +PROMPT_I+, +PROMPT_S+ and +PROMPT_C+ specify the format. In the
152 # prompt specification, some special strings are available:
153 #
154 # %N # command name which is running
155 # %m # to_s of main object (self)
156 # %M # inspect of main object (self)
157 # %l # type of string(", ', /, ]), `]' is inner %w[...]
158 # %NNi # indent level. NN is digits and means as same as printf("%NNd").
159 # # It can be omitted
160 # %NNn # line number.
161 # %% # %
162 #
163 # For instance, the default prompt mode is defined as follows:
164 #
165 # IRB.conf[:PROMPT_MODE][:DEFAULT] = {
166 # :PROMPT_I => "%N(%m):%03n:%i> ",
167 # :PROMPT_S => "%N(%m):%03n:%i%l ",
168 # :PROMPT_C => "%N(%m):%03n:%i* ",
169 # :RETURN => "%s\n" # used to printf
170 # }
171 #
172 # irb comes with a number of available modes:
173 #
174 # # :NULL:
175 # # :PROMPT_I:
176 # # :PROMPT_N:
177 # # :PROMPT_S:
178 # # :PROMPT_C:
179 # # :RETURN: |
180 # # %s
181 # # :DEFAULT:
182 # # :PROMPT_I: ! '%N(%m):%03n:%i> '
183 # # :PROMPT_N: ! '%N(%m):%03n:%i> '
184 # # :PROMPT_S: ! '%N(%m):%03n:%i%l '
185 # # :PROMPT_C: ! '%N(%m):%03n:%i* '
186 # # :RETURN: |
187 # # => %s
188 # # :CLASSIC:
189 # # :PROMPT_I: ! '%N(%m):%03n:%i> '
190 # # :PROMPT_N: ! '%N(%m):%03n:%i> '
191 # # :PROMPT_S: ! '%N(%m):%03n:%i%l '
192 # # :PROMPT_C: ! '%N(%m):%03n:%i* '
193 # # :RETURN: |
194 # # %s
195 # # :SIMPLE:
196 # # :PROMPT_I: ! '>> '
197 # # :PROMPT_N: ! '>> '
198 # # :PROMPT_S:
199 # # :PROMPT_C: ! '?> '
200 # # :RETURN: |
201 # # => %s
202 # # :INF_RUBY:
203 # # :PROMPT_I: ! '%N(%m):%03n:%i> '
204 # # :PROMPT_N:
205 # # :PROMPT_S:
206 # # :PROMPT_C:
207 # # :RETURN: |
208 # # %s
209 # # :AUTO_INDENT: true
210 # # :XMP:
211 # # :PROMPT_I:
212 # # :PROMPT_N:
213 # # :PROMPT_S:
214 # # :PROMPT_C:
215 # # :RETURN: |2
216 # # ==>%s
217 #
218 # == Restrictions
219 #
220 # Because irb evaluates input immediately after it is syntactically complete,
221 # the results may be slightly different than directly using Ruby.
222 #
223 # == IRB Sessions
224 #
225 # IRB has a special feature, that allows you to manage many sessions at once.
226 #
227 # You can create new sessions with Irb.irb, and get a list of current sessions
228 # with the +jobs+ command in the prompt.
229 #
230 # === Commands
231 #
232 # JobManager provides commands to handle the current sessions:
233 #
234 # jobs # List of current sessions
235 # fg # Switches to the session of the given number
236 # kill # Kills the session with the given number
237 #
238 # The +exit+ command, or ::irb_exit, will quit the current session and call any
239 # exit hooks with IRB.irb_at_exit.
240 #
241 # A few commands for loading files within the session are also available:
242 #
243 # +source+::
244 # Loads a given file in the current session and displays the source lines,
245 # see IrbLoader#source_file
246 # +irb_load+::
247 # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load
248 # +irb_require+::
249 # Loads the given file similarly to Kernel#require
250 #
251 # === Configuration
252 #
253 # The command line options, or IRB.conf, specify the default behavior of
254 # Irb.irb.
255 #
256 # On the other hand, each conf in IRB@Command+line+options is used to
257 # individually configure IRB.irb.
258 #
259 # If a proc is set for IRB.conf[:IRB_RC], its will be invoked after execution
260 # of that proc with the context of the current session as its argument. Each
261 # session can be configured using this mechanism.
262 #
263 # === Session variables
264 #
265 # There are a few variables in every Irb session that can come in handy:
266 #
267 # <code>_</code>::
268 # The value command executed, as a local variable
269 # <code>__</code>::
270 # The history of evaluated commands
271 # <code>__[line_no]</code>::
272 # Returns the evaluation value at the given line number, +line_no+.
273 # If +line_no+ is a negative, the return value +line_no+ many lines before
274 # the most recent return value.
275 #
276 # === Example using IRB Sessions
277 #
278 # # invoke a new session
279 # irb(main):001:0> irb
280 # # list open sessions
281 # irb.1(main):001:0> jobs
282 # #0->irb on main (#<Thread:0x400fb7e4> : stop)
283 # #1->irb#1 on main (#<Thread:0x40125d64> : running)
284 #
285 # # change the active session
286 # irb.1(main):002:0> fg 0
287 # # define class Foo in top-level session
288 # irb(main):002:0> class Foo;end
289 # # invoke a new session with the context of Foo
290 # irb(main):003:0> irb Foo
291 # # define Foo#foo
292 # irb.2(Foo):001:0> def foo
293 # irb.2(Foo):002:1> print 1
294 # irb.2(Foo):003:1> end
295 #
296 # # change the active session
297 # irb.2(Foo):004:0> fg 0
298 # # list open sessions
299 # irb(main):004:0> jobs
300 # #0->irb on main (#<Thread:0x400fb7e4> : running)
301 # #1->irb#1 on main (#<Thread:0x40125d64> : stop)
302 # #2->irb#2 on Foo (#<Thread:0x4011d54c> : stop)
303 # # check if Foo#foo is available
304 # irb(main):005:0> Foo.instance_methods #=> [:foo, ...]
305 #
306 # # change the active sesssion
307 # irb(main):006:0> fg 2
308 # # define Foo#bar in the context of Foo
309 # irb.2(Foo):005:0> def bar
310 # irb.2(Foo):006:1> print "bar"
311 # irb.2(Foo):007:1> end
312 # irb.2(Foo):010:0> Foo.instance_methods #=> [:bar, :foo, ...]
313 #
314 # # change the active session
315 # irb.2(Foo):011:0> fg 0
316 # irb(main):007:0> f = Foo.new #=> #<Foo:0x4010af3c>
317 # # invoke a new session with the context of f (instance of Foo)
318 # irb(main):008:0> irb f
319 # # list open sessions
320 # irb.3(<Foo:0x4010af3c>):001:0> jobs
321 # #0->irb on main (#<Thread:0x400fb7e4> : stop)
322 # #1->irb#1 on main (#<Thread:0x40125d64> : stop)
323 # #2->irb#2 on Foo (#<Thread:0x4011d54c> : stop)
324 # #3->irb#3 on #<Foo:0x4010af3c> (#<Thread:0x4010a1e0> : running)
325 # # evaluate f.foo
326 # irb.3(<Foo:0x4010af3c>):002:0> foo #=> 1 => nil
327 # # evaluate f.bar
328 # irb.3(<Foo:0x4010af3c>):003:0> bar #=> bar => nil
329 # # kill jobs 1, 2, and 3
330 # irb.3(<Foo:0x4010af3c>):004:0> kill 1, 2, 3
331 # # list open sessions, should only include main session
332 # irb(main):009:0> jobs
333 # #0->irb on main (#<Thread:0x400fb7e4> : running)
334 # # quit irb
335 # irb(main):010:0> exit
336 module IRB
337
338 # An exception raised by IRB.irb_abort
339 class Abort < Exception;end
340
341 @CONF = {}
342
343
344 # Displays current configuration.
345 #
346 # Modifying the configuration is achieved by sending a message to IRB.conf.
347 #
348 # See IRB@Configuration for more information.
349 def IRB.conf
350 @CONF
351 end
352
353 # Returns the current version of IRB, including release version and last
354 # updated date.
355 def IRB.version
356 if v = @CONF[:VERSION] then return v end
357
358 @CONF[:VERSION] = format("irb %s (%s)", @RELEASE_VERSION, @LAST_UPDATE_DATE)
359 end
360
361 # The current IRB::Context of the session, see IRB.conf
362 #
363 # irb
364 # irb(main):001:0> IRB.CurrentContext.irb_name = "foo"
365 # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo"
366 def IRB.CurrentContext
367 IRB.conf[:MAIN_CONTEXT]
368 end
369
370 # Initializes IRB and creates a new Irb.irb object at the +TOPLEVEL_BINDING+
371 def IRB.start(ap_path = nil)
372 STDOUT.sync = true
373 $0 = File::basename(ap_path, ".rb") if ap_path
374
375 IRB.setup(ap_path)
376
377 if @CONF[:SCRIPT]
378 irb = Irb.new(nil, @CONF[:SCRIPT])
379 else
380 irb = Irb.new
381 end
382 irb.run(@CONF)
383 end
384
385 # Calls each event hook of IRB.conf[:AT_EXIT] when the current session quits.
386 def IRB.irb_at_exit
387 @CONF[:AT_EXIT].each{|hook| hook.call}
388 end
389
390 # Quits irb
391 def IRB.irb_exit(irb, ret)
392 throw :IRB_EXIT, ret
393 end
394
395 # Aborts then interrupts irb.
396 #
397 # Will raise an Abort exception, or the given +exception+.
398 def IRB.irb_abort(irb, exception = Abort)
399 if defined? Thread
400 irb.context.thread.raise exception, "abort then interrupt!"
401 else
402 raise exception, "abort then interrupt!"
403 end
404 end
405
406 class Irb
407 # Creates a new irb session
408 def initialize(workspace = nil, input_method = nil, output_method = nil)
409 @context = Context.new(self, workspace, input_method, output_method)
410 @context.main.extend ExtendCommandBundle
411 @signal_status = :IN_IRB
412
413 @scanner = RubyLex.new
414 @scanner.exception_on_syntax_error = false
415 end
416
417 def run(conf = IRB.conf)
418 conf[:IRB_RC].call(context) if conf[:IRB_RC]
419 conf[:MAIN_CONTEXT] = context
420
421 trap("SIGINT") do
422 signal_handle
423 end
424
425 begin
426 catch(:IRB_EXIT) do
427 eval_input
428 end
429 ensure
430 conf[:AT_EXIT].each{|hook| hook.call}
431 end
432 end
433
434 # Returns the current context of this irb session
435 attr_reader :context
436 # The lexer used by this irb session
437 attr_accessor :scanner
438
439 # Evaluates input for this session.
440 def eval_input
441 exc = nil
442
443 @scanner.set_prompt do
444 |ltype, indent, continue, line_no|
445 if ltype
446 f = @context.prompt_s
447 elsif continue
448 f = @context.prompt_c
449 elsif indent > 0
450 f = @context.prompt_n
451 else
452 f = @context.prompt_i
453 end
454 f = "" unless f
455 if @context.prompting?
456 @context.io.prompt = p = prompt(f, ltype, indent, line_no)
457 else
458 @context.io.prompt = p = ""
459 end
460 if @context.auto_indent_mode
461 unless ltype
462 ind = prompt(@context.prompt_i, ltype, indent, line_no)[/.*\z/].size +
463 indent * 2 - p.size
464 ind += 2 if continue
465 @context.io.prompt = p + " " * ind if ind > 0
466 end
467 end
468 end
469
470 @scanner.set_input(@context.io) do
471 signal_status(:IN_INPUT) do
472 if l = @context.io.gets
473 print l if @context.verbose?
474 else
475 if @context.ignore_eof? and @context.io.readable_after_eof?
476 l = "\n"
477 if @context.verbose?
478 printf "Use \"exit\" to leave %s\n", @context.ap_name
479 end
480 else
481 print "\n"
482 end
483 end
484 l
485 end
486 end
487
488 @scanner.each_top_level_statement do |line, line_no|
489 signal_status(:IN_EVAL) do
490 begin
491 line.untaint
492 @context.evaluate(line, line_no, exception: exc)
493 output_value if @context.echo?
494 rescue Interrupt => exc
495 rescue SystemExit, SignalException
496 raise
497 rescue Exception => exc
498 else
499 exc = nil
500 next
501 end
502 handle_exception(exc)
503 end
504 end
505 end
506
507 def handle_exception(exc)
508 if exc.backtrace && exc.backtrace[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
509 !(SyntaxError === exc)
510 irb_bug = true
511 else
512 irb_bug = false
513 end
514
515 if STDOUT.tty?
516 attr = ATTR_TTY
517 print "#{attr[1]}Traceback#{attr[]} (most recent call last):\n"
518 else
519 attr = ATTR_PLAIN
520 end
521 messages = []
522 lasts = []
523 levels = 0
524 if exc.backtrace
525 count = 0
526 exc.backtrace.each do |m|
527 m = @context.workspace.filter_backtrace(m) or next unless irb_bug
528 count += 1
529 if attr == ATTR_TTY
530 m = sprintf("%9d: from %s", count, m)
531 else
532 m = "\tfrom #{m}"
533 end
534 if messages.size < @context.back_trace_limit
535 messages.push(m)
536 elsif lasts.size < @context.back_trace_limit
537 lasts.push(m).shift
538 levels += 1
539 end
540 end
541 end
542 if attr == ATTR_TTY
543 unless lasts.empty?
544 puts lasts.reverse
545 printf "... %d levels...\n", levels if levels > 0
546 end
547 puts messages.reverse
548 end
549 m = exc.to_s.split(/\n/)
550 print "#{attr[1]}#{exc.class} (#{attr[4]}#{m.shift}#{attr[0, 1]})#{attr[]}\n"
551 puts m.map {|s| "#{attr[1]}#{s}#{attr[]}\n"}
552 if attr == ATTR_PLAIN
553 puts messages
554 unless lasts.empty?
555 puts lasts
556 printf "... %d levels...\n", levels if levels > 0
557 end
558 end
559 print "Maybe IRB bug!\n" if irb_bug
560 end
561
562 # Evaluates the given block using the given +path+ as the Context#irb_path
563 # and +name+ as the Context#irb_name.
564 #
565 # Used by the irb command +source+, see IRB@IRB+Sessions for more
566 # information.
567 def suspend_name(path = nil, name = nil)
568 @context.irb_path, back_path = path, @context.irb_path if path
569 @context.irb_name, back_name = name, @context.irb_name if name
570 begin
571 yield back_path, back_name
572 ensure
573 @context.irb_path = back_path if path
574 @context.irb_name = back_name if name
575 end
576 end
577
578 # Evaluates the given block using the given +workspace+ as the
579 # Context#workspace.
580 #
581 # Used by the irb command +irb_load+, see IRB@IRB+Sessions for more
582 # information.
583 def suspend_workspace(workspace)
584 @context.workspace, back_workspace = workspace, @context.workspace
585 begin
586 yield back_workspace
587 ensure
588 @context.workspace = back_workspace
589 end
590 end
591
592 # Evaluates the given block using the given +input_method+ as the
593 # Context#io.
594 #
595 # Used by the irb commands +source+ and +irb_load+, see IRB@IRB+Sessions
596 # for more information.
597 def suspend_input_method(input_method)
598 back_io = @context.io
599 @context.instance_eval{@io = input_method}
600 begin
601 yield back_io
602 ensure
603 @context.instance_eval{@io = back_io}
604 end
605 end
606
607 # Evaluates the given block using the given +context+ as the Context.
608 def suspend_context(context)
609 @context, back_context = context, @context
610 begin
611 yield back_context
612 ensure
613 @context = back_context
614 end
615 end
616
617 # Handler for the signal SIGINT, see Kernel#trap for more information.
618 def signal_handle
619 unless @context.ignore_sigint?
620 print "\nabort!\n" if @context.verbose?
621 exit
622 end
623
624 case @signal_status
625 when :IN_INPUT
626 print "^C\n"
627 raise RubyLex::TerminateLineInput
628 when :IN_EVAL
629 IRB.irb_abort(self)
630 when :IN_LOAD
631 IRB.irb_abort(self, LoadAbort)
632 when :IN_IRB
633 # ignore
634 else
635 # ignore other cases as well
636 end
637 end
638
639 # Evaluates the given block using the given +status+.
640 def signal_status(status)
641 return yield if @signal_status == :IN_LOAD
642
643 signal_status_back = @signal_status
644 @signal_status = status
645 begin
646 yield
647 ensure
648 @signal_status = signal_status_back
649 end
650 end
651
652 def prompt(prompt, ltype, indent, line_no) # :nodoc:
653 p = prompt.dup
654 p.gsub!(/%([0-9]+)?([a-zA-Z])/) do
655 case $2
656 when "N"
657 @context.irb_name
658 when "m"
659 @context.main.to_s
660 when "M"
661 @context.main.inspect
662 when "l"
663 ltype
664 when "i"
665 if $1
666 format("%" + $1 + "d", indent)
667 else
668 indent.to_s
669 end
670 when "n"
671 if $1
672 format("%" + $1 + "d", line_no)
673 else
674 line_no.to_s
675 end
676 when "%"
677 "%"
678 end
679 end
680 p
681 end
682
683 def output_value # :nodoc:
684 printf @context.return_format, @context.inspect_last_value
685 end
686
687 # Outputs the local variables to this current session, including
688 # #signal_status and #context, using IRB::Locale.
689 def inspect
690 ary = []
691 for iv in instance_variables
692 case (iv = iv.to_s)
693 when "@signal_status"
694 ary.push format("%s=:%s", iv, @signal_status.id2name)
695 when "@context"
696 ary.push format("%s=%s", iv, eval(iv).__to_s__)
697 else
698 ary.push format("%s=%s", iv, eval(iv))
699 end
700 end
701 format("#<%s: %s>", self.class, ary.join(", "))
702 end
703
704 ATTR_TTY = "\e[%sm"
705 def ATTR_TTY.[](*a) self % a.join(";"); end
706 ATTR_PLAIN = ""
707 def ATTR_PLAIN.[](*) self; end
708 end
709
710 def @CONF.inspect
711 IRB.version unless self[:VERSION]
712
713 array = []
714 for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name}
715 case k
716 when :MAIN_CONTEXT, :__TMP__EHV__
717 array.push format("CONF[:%s]=...myself...", k.id2name)
718 when :PROMPT
719 s = v.collect{
720 |kk, vv|
721 ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"}
722 format(":%s=>{%s}", kk.id2name, ss.join(", "))
723 }
724 array.push format("CONF[:%s]={%s}", k.id2name, s.join(", "))
725 else
726 array.push format("CONF[:%s]=%s", k.id2name, v.inspect)
727 end
728 end
729 array.join("\n")
730 end
731 end
732
733 class Binding
734 # Opens an IRB session where +binding.irb+ is called which allows for
735 # interactive debugging. You can call any methods or variables available in
736 # the current scope, and mutate state if you need to.
737 #
738 #
739 # Given a Ruby file called +potato.rb+ containing the following code:
740 #
741 # class Potato
742 # def initialize
743 # @cooked = false
744 # binding.irb
745 # puts "Cooked potato: #{@cooked}"
746 # end
747 # end
748 #
749 # Potato.new
750 #
751 # Running +ruby potato.rb+ will open an IRB session where +binding.irb+ is
752 # called, and you will see the following:
753 #
754 # $ ruby potato.rb
755 #
756 # From: potato.rb @ line 4 :
757 #
758 # 1: class Potato
759 # 2: def initialize
760 # 3: @cooked = false
761 # => 4: binding.irb
762 # 5: puts "Cooked potato: #{@cooked}"
763 # 6: end
764 # 7: end
765 # 8:
766 # 9: Potato.new
767 #
768 # irb(#<Potato:0x00007feea1916670>):001:0>
769 #
770 # You can type any valid Ruby code and it will be evaluated in the current
771 # context. This allows you to debug without having to run your code repeatedly:
772 #
773 # irb(#<Potato:0x00007feea1916670>):001:0> @cooked
774 # => false
775 # irb(#<Potato:0x00007feea1916670>):002:0> self.class
776 # => Potato
777 # irb(#<Potato:0x00007feea1916670>):003:0> caller.first
778 # => ".../2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'"
779 # irb(#<Potato:0x00007feea1916670>):004:0> @cooked = true
780 # => true
781 #
782 # You can exit the IRB session with the `exit` command. Note that exiting will
783 # resume execution where +binding.irb+ had paused it, as you can see from the
784 # output printed to standard output in this example:
785 #
786 # irb(#<Potato:0x00007feea1916670>):005:0> exit
787 # Cooked potato: true
788 #
789 #
790 # See IRB@IRB+Usage for more information.
791 def irb
792 IRB.setup(eval("__FILE__"), argv: [])
793 workspace = IRB::WorkSpace.new(self)
794 STDOUT.print(workspace.code_around_binding)
795 IRB::Irb.new(workspace).run(IRB.conf)
796 end
797 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
-98
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 return nil if (ENV["LC_ALL"] || ENV["LC_MESSAGES"] || ENV["LANG"]) == "C"
60 locales = ENV["LANGUAGE"]
61 if (locales != nil and locales.size > 0)
62 locs = locales.split(/:/).collect{|v| Locale::Tag::Posix.parse(v)}.compact
63 if locs.size > 0
64 return Locale::TagList.new(locs)
65 end
66 elsif (loc = locale)
67 return Locale::TagList.new([loc])
68 end
69 nil
70 end
71
72 # Gets the charset from environment variables
73 # (LC_ALL > LC_CTYPE > LANG) or return nil.
74 # * Returns: the system charset.
75 def charset # :nodoc:
76 [ENV["LC_ALL"], ENV["LC_CTYPE"], ENV["LANG"]].each do |env|
77 tag = Private.parse(env)
78 next if tag.nil?
79 return tag.charset
80 end
81 nil
82 end
83
84 module Private
85 module_function
86 def parse(env_value)
87 return nil if env_value.nil?
88 return nil if env_value.empty?
89 Locale::Tag::Posix.parse(env_value)
90 end
91 end
92 end
93
94 MODULES[:env] = Env
95 end
96 end
97
+0
-62
vendor/locale/driver/jruby.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2007-2008 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require 'java'
22
23 require "locale/driver/env"
24
25 module Locale
26 module Driver
27 # Locale::Driver::JRuby module for JRuby
28 # Detect the user locales and the charset.
29 # This is a low-level class. Application shouldn't use this directly.
30 module JRuby
31 $stderr.puts self.name + " is loaded." if $DEBUG
32
33 module_function
34 def locales #:nodoc:
35 locales = ::Locale::Driver::Env.locales
36 unless locales
37 locale = java.util.Locale.getDefault
38 variant = locale.getVariant
39 variants = []
40 if variant != nil and variant.size > 0
41 variants = [variant]
42 end
43 locales = TagList.new([Locale::Tag::Common.new(locale.getLanguage, nil,
44 locale.getCountry,
45 variants)])
46 end
47 locales
48 end
49
50 def charset #:nodoc:
51 charset = ::Locale::Driver::Env.charset
52 unless charset
53 charset = java.nio.charset.Charset.defaultCharset.name
54 end
55 charset
56 end
57 end
58
59 MODULES[:jruby] = JRuby
60 end
61 end
+0
-58
vendor/locale/driver/posix.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2002-2008 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "locale/driver/env"
22
23 module Locale
24 # Locale::Driver::Posix module for Posix OS (Unix)
25 # Detect the user locales and the charset.
26 # This is a low-level class. Application shouldn't use this directly.
27 module Driver
28 module Posix
29 $stderr.puts self.name + " is loaded." if $DEBUG
30
31 module_function
32 # Gets the locales from environment variables. (LANGUAGE > LC_ALL > LC_MESSAGES > LANG)
33 # Only LANGUAGE accept plural languages such as "nl_BE;
34 # * Returns: an Array of the locale as Locale::Tag::Posix or nil.
35 def locales
36 ::Locale::Driver::Env.locales
37 end
38
39 # Gets the charset from environment variable or the result of
40 # "locale charmap" or nil.
41 # * Returns: the system charset.
42 def charset
43 charset = ::Locale::Driver::Env.charset
44 unless charset
45 charset = `locale charmap`.strip
46 unless $? && $?.success?
47 charset = nil
48 end
49 end
50 charset
51 end
52 end
53
54 MODULES[:posix] = Posix
55 end
56 end
57
+0
-94
vendor/locale/driver/win32.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 # Copyright (C) 2002-2010 Masao Mutoh
4 #
5 # Original: Ruby-GetText-Package-1.92.0.
6 # License: Ruby's or LGPL
7 #
8 # This library is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 require "locale/driver/env"
22 require "locale/driver/win32_table"
23
24 require "fiddle/import"
25
26 module Locale
27 # Locale::Driver::Win32 module for win32.
28 # Detect the user locales and the charset.
29 # This is a low-level class. Application shouldn't use this directly.
30 module Driver
31 module Win32
32 module Kernel32
33 extend Fiddle::Importer
34 dlload "kernel32.dll"
35 extern "int GetThreadLocale()"
36 end
37
38 include Win32Table
39
40 $stderr.puts self.name + " is loaded." if $DEBUG
41
42 @@current_locale_id = nil
43
44 module_function
45
46 # Gets the Win32 charset of the locale.
47 def charset
48 charset = ::Locale::Driver::Env.charset
49 unless charset
50 if locales
51 tag = locales[0].to_rfc.to_s
52 loc = LocaleTable.find{|v| v[1] == tag}
53 loc = LocaleTable.find{|v| v[1] =~ /^#{locales[0].language}/} unless loc
54 charset = loc ? loc[2] : nil
55 else
56 charset = "CP1252"
57 end
58 end
59 charset
60 end
61
62 def thread_locale_id #:nodoc:
63 if @@current_locale_id
64 @@current_locale_id
65 else
66 Kernel32.GetThreadLocale
67 end
68 end
69
70 def set_thread_locale_id(lcid) #:nodoc:
71 # for testing.
72 @@current_locale_id = lcid
73 end
74
75 def locales #:nodoc:
76 locales = ::Locale::Driver::Env.locales
77 unless locales
78 lang = LocaleTable.assoc(thread_locale_id)
79 if lang
80 ret = Locale::Tag::Common.parse(lang[1])
81 locales = Locale::TagList.new([ret])
82 else
83 locales = nil
84 end
85 end
86 locales
87 end
88 end
89
90 MODULES[:win32] = Win32
91 end
92 end
93
+0
-298
vendor/locale/driver/win32_table.rb less more
0 =begin
1 win32_table.rb - Locale table for win32
2
3 Copyright (C) 2008 Masao Mutoh <mutomasa at gmail.com>
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 Original: Ruby-GetText-Package-1.92.0.
9
10 $Id: win32_table.rb 27 2008-12-03 15:06:50Z mutoh $
11 =end
12
13 module Locale
14 module Driver
15 module Win32Table
16
17 #http://msdn.microsoft.com/ja-jp/goglobal/bb896001(en-us).aspx
18
19 #LangID, locale name, code page
20 LocaleTable = [
21 [0x0036,"af","CP1252"],
22 [0x0436,"af-ZA","CP1252"],
23 [0x001C,"sq","CP1250"],
24 [0x041C,"sq-AL","CP1250"],
25 [0x0484,"gsw-FR","CP1252"],
26 [0x045E,"am-ET","UNICODE"],
27 [0x0001,"ar","CP1256"],
28 [0x1401,"ar-DZ","CP1256"],
29 [0x3C01,"ar-BH","CP1256"],
30 [0x0C01,"ar-EG","CP1256"],
31 [0x0801,"ar-IQ","CP1256"],
32 [0x2C01,"ar-JO","CP1256"],
33 [0x3401,"ar-KW","CP1256"],
34 [0x3001,"ar-LB","CP1256"],
35 [0x1001,"ar-LY","CP1256"],
36 [0x1801,"ar-MA","CP1256"],
37 [0x2001,"ar-OM","CP1256"],
38 [0x4001,"ar-QA","CP1256"],
39 [0x0401,"ar-SA","CP1256"],
40 [0x2801,"ar-SY","CP1256"],
41 [0x1C01,"ar-TN","CP1256"],
42 [0x3801,"ar-AE","CP1256"],
43 [0x2401,"ar-YE","CP1256"],
44 [0x002B,"hy","UNICODE"],
45 [0x042B,"hy-AM","UNICODE"],
46 [0x044D,"as-IN","UNICODE"],
47 [0x002C,"az","CP1254"],
48 [0x082C,"az-Cyrl-AZ","CP1251"],
49 [0x042C,"az-Latn-AZ","CP1254"],
50 [0x046D,"ba-RU","CP1251"],
51 [0x002D,"eu","CP1252"],
52 [0x042D,"eu-ES","CP1252"],
53 [0x0023,"be","CP1251"],
54 [0x0423,"be-BY","CP1251"],
55 [0x0845,"bn-BD","UNICODE"],
56 [0x0445,"bn-IN","UNICODE"],
57 [0x201A,"bs-Cyrl-BA","CP1251"],
58 [0x141A,"bs-Latn-BA","CP1250"],
59 [0x047E,"br-FR","CP1252"],
60 [0x0002,"bg","CP1251"],
61 [0x0402,"bg-BG","CP1251"],
62 [0x0003,"ca","CP1252"],
63 [0x0403,"ca-ES","CP1252"],
64 [0x0C04,"zh-HK","CP950"],
65 [0x1404,"zh-MO","CP950"],
66 [0x0804,"zh-CN","CP936"],
67 [0x0004,"zh-Hans","CP936"],
68 [0x1004,"zh-SG","CP936"],
69 [0x0404,"zh-TW","CP950"],
70 [0x7C04,"zh-Hant","CP950"],
71 [0x0483,"co-FR","CP1252"],
72 [0x001A,"hr","CP1250"],
73 [0x041A,"hr-HR","CP1250"],
74 [0x101A,"hr-BA","CP1250"],
75 [0x0005,"cs","CP1250"],
76 [0x0405,"cs-CZ","CP1250"],
77 [0x0006,"da","CP1252"],
78 [0x0406,"da-DK","CP1252"],
79 [0x048C,"prs-AF","CP1256"],
80 [0x0065,"div","UNICODE"],
81 [0x0465,"div-MV","UNICODE"],
82 [0x0013,"nl","CP1252"],
83 [0x0813,"nl-BE","CP1252"],
84 [0x0413,"nl-NL","CP1252"],
85 [0x0009,"en","CP1252"],
86 [0x0C09,"en-AU","CP1252"],
87 [0x2809,"en-BZ","CP1252"],
88 [0x1009,"en-CA","CP1252"],
89 [0x2409,"en-029","CP1252"],
90 [0x4009,"en-IN","CP1252"],
91 [0x1809,"en-IE","CP1252"],
92 [0x2009,"en-JM","CP1252"],
93 [0x4409,"en-MY","CP1252"],
94 [0x1409,"en-NZ","CP1252"],
95 [0x3409,"en-PH","CP1252"],
96 [0x4809,"en-SG","CP1252"],
97 [0x1C09,"en-ZA","CP1252"],
98 [0x2C09,"en-TT","CP1252"],
99 [0x0809,"en-GB","CP1252"],
100 [0x0409,"en-US","CP1252"],
101 [0x3009,"en-ZW","CP1252"],
102 [0x0025,"et","CP1257"],
103 [0x0425,"et-EE","CP1257"],
104 [0x0038,"fo","CP1252"],
105 [0x0438,"fo-FO","CP1252"],
106 [0x0464,"fil-PH","CP1252"],
107 [0x000B,"fi","CP1252"],
108 [0x040B,"fi-FI","CP1252"],
109 [0x000C,"fr","CP1252"],
110 [0x080C,"fr-BE","CP1252"],
111 [0x0C0C,"fr-CA","CP1252"],
112 [0x040C,"fr-FR","CP1252"],
113 [0x140C,"fr-LU","CP1252"],
114 [0x180C,"fr-MC","CP1252"],
115 [0x100C,"fr-CH","CP1252"],
116 [0x0462,"fy-NL","CP1252"],
117 [0x0056,"gl","CP1252"],
118 [0x0456,"gl-ES","CP1252"],
119 [0x0037,"ka","UNICODE"],
120 [0x0437,"ka-GE","UNICODE"],
121 [0x0007,"de","CP1252"],
122 [0x0C07,"de-AT","CP1252"],
123 [0x0407,"de-DE","CP1252"],
124 [0x1407,"de-LI","CP1252"],
125 [0x1007,"de-LU","CP1252"],
126 [0x0807,"de-CH","CP1252"],
127 [0x0008,"el","CP1253"],
128 [0x0408,"el-GR","CP1253"],
129 [0x046F,"kl-GL","CP1252"],
130 [0x0047,"gu","UNICODE"],
131 [0x0447,"gu-IN","UNICODE"],
132 [0x0468,"ha-Latn-NG","CP1252"],
133 [0x000D,"he","CP1255"],
134 [0x040D,"he-IL","CP1255"],
135 [0x0039,"hi","UNICODE"],
136 [0x0439,"hi-IN","UNICODE"],
137 [0x000E,"hu","CP1250"],
138 [0x040E,"hu-HU","CP1250"],
139 [0x000F,"is","CP1252"],
140 [0x040F,"is-IS","CP1252"],
141 [0x0470,"ig-NG","CP1252"],
142 [0x0021,"id","CP1252"],
143 [0x0421,"id-ID","CP1252"],
144 [0x085D,"iu-Latn-CA","CP1252"],
145 [0x045D,"iu-Cans-CA","UNICODE"],
146 [0x083C,"ga-IE","CP1252"],
147 [0x0434,"xh-ZA","CP1252"],
148 [0x0435,"zu-ZA","CP1252"],
149 [0x0010,"it","CP1252"],
150 [0x0410,"it-IT","CP1252"],
151 [0x0810,"it-CH","CP1252"],
152 [0x0011,"ja","CP932"],
153 [0x0411,"ja-JP","CP932"],
154 [0x004B,"kn","UNICODE"],
155 [0x044B,"kn-IN","UNICODE"],
156 [0x003F,"kk","CP1251"],
157 [0x043F,"kk-KZ","CP1251"],
158 [0x0453,"km-KH","UNICODE"],
159 [0x0486,"qut-GT","CP1252"],
160 [0x0487,"rw-RW","CP1252"],
161 [0x0041,"sw","CP1252"],
162 [0x0441,"sw-KE","CP1252"],
163 [0x0057,"kok","UNICODE"],
164 [0x0457,"kok-IN","UNICODE"],
165 [0x0012,"ko","CP949"],
166 [0x0412,"ko-KR","CP949"],
167 [0x0040,"ky","CP1251"],
168 [0x0440,"ky-KG","CP1251"],
169 [0x0454,"lo-LA","UNICODE"],
170 [0x0026,"lv","CP1257"],
171 [0x0426,"lv-LV","CP1257"],
172 [0x0027,"lt","CP1257"],
173 [0x0427,"lt-LT","CP1257"],
174 [0x082E,"wee-DE","CP1252"],
175 [0x046E,"lb-LU","CP1252"],
176 [0x002F,"mk","CP1251"],
177 [0x042F,"mk-MK","CP1251"],
178 [0x003E,"ms","CP1252"],
179 [0x083E,"ms-BN","CP1252"],
180 [0x043E,"ms-MY","CP1252"],
181 [0x044C,"ml-IN","UNICODE"],
182 [0x043A,"mt-MT","UNICODE"],
183 [0x0481,"mi-NZ","UNICODE"],
184 [0x047A,"arn-CL","CP1252"],
185 [0x004E,"mr","UNICODE"],
186 [0x044E,"mr-IN","UNICODE"],
187 [0x047C,"moh-CA","CP1252"],
188 [0x0050,"mn","CP1251"],
189 [0x0450,"mn-MN","CP1251"],
190 [0x0850,"mn-Mong-CN","UNICODE"],
191 [0x0461,"ne-NP","UNICODE"],
192 [0x0014,"no","CP1252"],
193 [0x0414,"nb-NO","CP1252"],
194 [0x0814,"nn-NO","CP1252"],
195 [0x0482,"oc-FR","CP1252"],
196 [0x0448,"or-IN","UNICODE"],
197 [0x0463,"ps-AF","UNICODE"],
198 [0x0029,"fa","CP1256"],
199 [0x0429,"fa-IR","CP1256"],
200 [0x0015,"pl","CP1250"],
201 [0x0415,"pl-PL","CP1250"],
202 [0x0016,"pt","CP1252"],
203 [0x0416,"pt-BR","CP1252"],
204 [0x0816,"pt-PT","CP1252"],
205 [0x0046,"pa","UNICODE"],
206 [0x0446,"pa-IN","UNICODE"],
207 [0x046B,"quz-BO","CP1252"],
208 [0x086B,"quz-EC","CP1252"],
209 [0x0C6B,"quz-PE","CP1252"],
210 [0x0018,"ro","CP1250"],
211 [0x0418,"ro-RO","CP1250"],
212 [0x0417,"rm-CH","CP1252"],
213 [0x0019,"ru","CP1251"],
214 [0x0419,"ru-RU","CP1251"],
215 [0x243B,"smn-FI","CP1252"],
216 [0x103B,"smj-NO","CP1252"],
217 [0x143B,"smj-SE","CP1252"],
218 [0x0C3B,"se-FI","CP1252"],
219 [0x043B,"se-NO","CP1252"],
220 [0x083B,"se-SE","CP1252"],
221 [0x203B,"sms-FI","CP1252"],
222 [0x183B,"sma-NO","CP1252"],
223 [0x1C3B,"sma-SE","CP1252"],
224 [0x004F,"sa","UNICODE"],
225 [0x044F,"sa-IN","UNICODE"],
226 [0x7C1A,"sr","CP1251"],
227 [0x1C1A,"sr-Cyrl-BA","CP1251"],
228 [0x0C1A,"sr-Cyrl-SP","CP1251"],
229 [0x181A,"sr-Latn-BA","CP1250"],
230 [0x081A,"sr-Latn-SP","CP1250"],
231 [0x046C,"nso-ZA","CP1252"],
232 [0x0432,"tn-ZA","CP1252"],
233 [0x045B,"si-LK","UNICODE"],
234 [0x001B,"sk","CP1250"],
235 [0x041B,"sk-SK","CP1250"],
236 [0x0024,"sl","CP1250"],
237 [0x0424,"sl-SI","CP1250"],
238 [0x000A,"es","CP1252"],
239 [0x2C0A,"es-AR","CP1252"],
240 [0x400A,"es-BO","CP1252"],
241 [0x340A,"es-CL","CP1252"],
242 [0x240A,"es-CO","CP1252"],
243 [0x140A,"es-CR","CP1252"],
244 [0x1C0A,"es-DO","CP1252"],
245 [0x300A,"es-EC","CP1252"],
246 [0x440A,"es-SV","CP1252"],
247 [0x100A,"es-GT","CP1252"],
248 [0x480A,"es-HN","CP1252"],
249 [0x080A,"es-MX","CP1252"],
250 [0x4C0A,"es-NI","CP1252"],
251 [0x180A,"es-PA","CP1252"],
252 [0x3C0A,"es-PY","CP1252"],
253 [0x280A,"es-PE","CP1252"],
254 [0x500A,"es-PR","CP1252"],
255 [0x0C0A,"es-ES","CP1252"],
256 [0x540A,"es-US","CP1252"],
257 [0x380A,"es-UY","CP1252"],
258 [0x200A,"es-VE","CP1252"],
259 [0x001D,"sv","CP1252"],
260 [0x081D,"sv-FI","CP1252"],
261 [0x041D,"sv-SE","CP1252"],
262 [0x005A,"syr","UNICODE"],
263 [0x045A,"syr-SY","UNICODE"],
264 [0x0428,"tg-Cyrl-TJ","CP1251"],
265 [0x085F,"tmz-Latn-DZ","CP1252"],
266 [0x0049,"ta","UNICODE"],
267 [0x0449,"ta-IN","UNICODE"],
268 [0x0044,"tt","CP1251"],
269 [0x0444,"tt-RU","CP1251"],
270 [0x004A,"te","UNICODE"],
271 [0x044A,"te-IN","UNICODE"],
272 [0x001E,"th","CP874"],
273 [0x041E,"th-TH","CP874"],
274 [0x0451,"bo-CN","UNICODE"],
275 [0x001F,"tr","CP1254"],
276 [0x041F,"tr-TR","CP1254"],
277 [0x0442,"tk-TM","CP1250"],
278 [0x0480,"ug-CN","CP1256"],
279 [0x0022,"uk","CP1251"],
280 [0x0422,"uk-UA","CP1251"],
281 [0x042E,"wen-DE","CP1252"],
282 [0x0020,"ur","CP1256"],
283 [0x0420,"ur-PK","CP1256"],
284 [0x0043,"uz","CP1254"],
285 [0x0843,"uz-Cyrl-UZ","CP1251"],
286 [0x0443,"uz-Latn-UZ","CP1254"],
287 [0x002A,"vi","CP1258"],
288 [0x042A,"vi-VN","CP1258"],
289 [0x0452,"cy-GB","CP1252"],
290 [0x0488,"wo-SN","CP1252"],
291 [0x0485,"sah-RU","CP1251"],
292 [0x0478,"ii-CN","UNICODE"],
293 [0x046A,"yo-NG","CP1252"],
294 ]
295 end
296 end
297 end
+0
-24
vendor/locale/driver.rb less more
0 # -*- coding: utf-8 -*-
1 #
2 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3 #
4 # License: Ruby's or LGPL
5 #
6 # This library is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 module Locale
20 module Driver
21 MODULES = {}
22 end
23 end
+0
-139
vendor/locale/info/language.rb less more
0 # encoding: UTF-8
1 =begin
2
3 language.rb - Locale::Info::Language class
4
5 Copyright (C) 2008 Masao Mutoh
6
7 Original Author:: Brian Pontarelli
8
9 $Id: language.rb 27 2008-12-03 15:06:50Z mutoh $
10 =end
11
12 require 'zlib'
13
14 module Locale
15
16 module Info
17 # This class contains all the of the ISO information for the ISO 639-3
18 # languages. This class is immutable once constructed.
19 class Language
20 attr_reader :two_code, :three_code, :scope, :type, :name
21
22 #
23 # Constructs a new Language instance.
24 #
25 # * code The 2 or 3 digit ISO 639-3 language code.
26 # * scope A single character that defines the ISO scope of the language - <tt>(I)ndividual</tt>,
27 # <tt>(M)acrolanguage</tt>, or <tt>(S)pecial</tt>.
28 # * type A single character that defines the ISO type of the language - <tt>(A)ncient</tt>,
29 # <tt>(C)onstructed</tt>, <tt>(E)xtinct</tt>, <tt>(H)istorical</tt>, <tt>(L)iving</tt>,
30 # or <tt>(S)pecial</tt>.
31 # * name The name of the language.
32 #
33 def initialize(two_code, three_code, scope, type, name)
34 @two_code, @three_code, @scope, @type, @name = two_code, three_code, scope, type, name
35
36 @individual = (scope == "I")
37 @macro = (scope == "M")
38 @special = (scope == "S")
39 @constructed = (type == "C")
40 @living = (type == "L")
41 @extinct = (type == "E")
42 @ancient = (type == "A")
43 @historical = (type == "H")
44 @special_type = (type == "S")
45 end
46
47 # Returns true if the language is an individual language according to the ISO 639-3 data.
48 def individual?; @individual; end
49
50 # Returns true if the language is a macro language according to the ISO 639-3 data.
51 def macro?; @macro; end
52
53 # Returns true if the language is a special language according to the ISO 639-3 data.
54 def special?; @special; end
55
56 # Returns true if the language is a constructed language according to the ISO 639-3 data.
57 def constructed?; @constructed; end
58
59 # Returns true if the language is a living language according to the ISO 639-3 data.
60 def living?; @living; end
61
62 # Returns true if the language is an extinct language according to the ISO 639-3 data.
63 def extinct?; @extinct; end
64
65 # Returns true if the language is an ancient language according to the ISO 639-3 data.
66 def ancient?; @ancient; end
67
68 # Returns true if the language is an historical language according to the ISO 639-3 data.
69 def historical?; @historical; end
70
71 # Returns true if the language is a special type language according to the ISO 639-3 data.
72 def special_type?; @special_type; end
73
74 # Returns the two or three code.
75 def to_s
76 if two_code and two_code.size > 0
77 two_code
78 else
79 three_code
80 end
81 end
82
83 # Returns this object is valid as ISO 639 data.
84 def iso_language?
85 @@lang_two_codes[two_code] != nil || @@lang_three_codes[three_code] != nil
86 end
87 end
88
89 @@lang_two_codes = Hash.new
90 @@lang_three_codes = Hash.new
91
92 Zlib::GzipReader.open(File.dirname(__FILE__) + "/../data/languages.tab.gz") do |gz|
93 gz.readlines.each do |l|
94 l.force_encoding('UTF-8') if l.respond_to?(:force_encoding)
95 unless l =~ /^\s*$/
96 parts = l.split(/\t/)
97 lang = Language.new(parts[2], parts[0], parts[3], parts[4], parts[5].strip)
98 @@lang_three_codes[parts[0]] = lang
99 @@lang_two_codes[parts[2]] = lang if parts[2].length > 0
100 end
101 end
102 end
103
104 module_function
105
106 # Returns a hash of all the ISO languages. The hash is {String, language} where
107 # the string is the 3 digit language code from the ISO 639 data. This contains
108 # all of the data from the ISO 639-3 data (7600 Languages).
109 #
110 # Need to require 'locale/info' or 'locale/language'.
111 def three_languages
112 @@lang_three_codes
113 end
114
115 # Returns a hash of all the ISO languages. The hash is {String, language} where
116 # the string is the 2 digit language code from the ISO 639-1 data. This contains
117 # all of the data from the ISO 639-1 data (186 Languages).
118 #
119 # Need to require 'locale/info' or 'locale/language'.
120 def two_languages
121 @@lang_two_codes
122 end
123
124 # Returns the language for the given 2 or 3 digit code.
125 #
126 # Need to require 'locale/info' or 'locale/language'.
127 def get_language(code)
128 @@lang_three_codes[code] || @@lang_two_codes[code]
129 end
130
131 # Returns the language code is valid.
132 #
133 # Need to require 'locale/info' or 'locale/language'.
134 def language_code?(code)
135 get_language(code) != nil
136 end
137 end
138 end
+0
-75
vendor/locale/info/region.rb less more
0 # encoding: UTF-8
1 =begin
2
3 region.rb - Locale::Info::Region class
4
5 Copyright (C) 2008 Masao Mutoh
6
7 First Author:: Brian Pontarelli
8
9 $Id: region.rb 27 2008-12-03 15:06:50Z mutoh $
10 =end
11
12 require 'zlib'
13
14 module Locale
15
16 module Info
17 # This class models out a region/country from the ISO 3166 standard for region codes.
18 # In ISO3166, it's called "Country" but Ruby/Locale the word "Region" instead.
19 class Region
20 attr_reader :code, :name
21
22 # code:: The 2 or 3 digit ISO 3166 region code.
23 # name:: The name of the region.
24 def initialize(code, name)
25 @code = code
26 @name = name
27 end
28
29 def iso_region?
30 @@regions[code] != nil
31 end
32
33 def to_s
34 "#{code}"
35 end
36 end
37
38 @@regions = Hash.new
39 Zlib::GzipReader.open(File.dirname(__FILE__) + "/../data/regions.tab.gz") do |gz|
40 gz.readlines.each do |l|
41 l.force_encoding('UTF-8') if l.respond_to?(:force_encoding)
42 unless l =~ /^\s*$/
43 parts = l.split(/\t/)
44 region = Region.new(parts[0], parts[1].strip)
45 @@regions[parts[0]] = region
46 end
47 end
48 end
49
50 module_function
51
52 # Returns a hash of all the ISO regions. The hash is {String, Region} where
53 # the string is the 2 digit region code from the ISO 3166 data.
54 #
55 # You need to require 'locale/info' or 'locale/region'.
56 def regions
57 @@regions
58 end
59
60 # Returns the region for the given code.
61 #
62 # You need to require 'locale/info' or 'locale/info/region'.
63 def get_region(code)
64 @@regions[code]
65 end
66
67 # Returns the region code is valid.
68 #
69 # You need to require 'locale/info' or 'locale/info/region'.
70 def valid_region_code?(code)
71 @@regions[code] != nil
72 end
73 end
74 end
+0
-12
vendor/locale/info.rb less more
0 =begin
1
2 info.rb - Load Locale::Info::Language and Locale::Info::Region.
3
4 Copyright (C) 2008 Masao Mutoh
5
6 $Id: info.rb 27 2008-12-03 15:06:50Z mutoh $
7 =end
8
9 require 'locale/info/language'
10 require 'locale/info/region'
11
+0
-38
vendor/locale/middleware.rb less more
0 # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
1 #
2 # License: Ruby's or LGPL
3 #
4 # This library is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Lesser General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 require "locale"
18
19 module Locale
20 class Middleware
21 def initialize(application, options={})
22 @application = application
23 @options = options
24 Locale.init(:driver => :cgi)
25 end
26
27 def call(environment)
28 request = Rack::Request.new(environment)
29 Locale.set_request([request["lang"]],
30 [request.cookies["lang"]],
31 environment["HTTP_ACCEPT_LANGUAGE"],
32 environment["HTTP_ACCEPT_CHARSET"])
33 @application.call(environment)
34 end
35 end
36 end
37
+0
-96
vendor/locale/tag/cldr.rb less more
0 =begin
1 locale/tag/cldr.rb - Locale::Tag::CLDR
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/common'
10
11 module Locale
12 module Tag
13
14 # Unicode locale identifier class for CLDR-1.6.1.
15 # (Unicode Common Locale Data Repository).
16 class Cldr < Common
17
18 VARIANT = "(#{ALPHANUM}{5,8}|#{DIGIT}#{ALPHANUM}{3})"
19 EXTENSION = "#{ALPHANUM}+=[a-z0-9\-]+"
20
21 TAG_RE = /\A#{LANGUAGE}(?:[-_]#{SCRIPT})?
22 (?:[-_]#{REGION})?((?:[-_]#{VARIANT})*
23 (?:@(#{EXTENSION};?)+)*)\Z/ix
24
25 attr_reader :extensions
26
27 class << self
28 # Parse the language tag and return the new Locale::Tag::CLDR.
29 def parse(tag)
30 if tag =~ /\APOSIX\Z/ # This is the special case of POSIX locale but match this regexp.
31 nil
32 elsif tag =~ TAG_RE
33 lang, script, region, subtag = $1, $2, $3, $4
34
35 extensions = {}
36 subtag.scan(/#{EXTENSION}/i).each{|v|
37 subtag.sub!(v, "")
38 key, type = v.split("=")
39 extensions[key] = type
40 }
41 variants = subtag.scan(/#{VARIANT}/i).collect{|v| v[0].upcase}
42
43 ret = self.new(lang, script, region, variants, extensions)
44 ret.tag = tag
45 ret
46 else
47 nil
48 end
49 end
50 end
51
52 # Create Locale::Tag::Cldr.
53 #
54 # variants should be upcase.
55 def initialize(language, script = nil, region = nil,
56 variants = [], extensions = {})
57 @extensions = extensions
58 super(language, script, region, variants.map{|v| v.upcase})
59 end
60
61 # Sets the extensions as an Hash.
62 def extensions=(val)
63 @extensions = val
64 end
65
66 private
67 def convert_to(klass) # :nodoc:
68 if klass == Cldr
69 klass.new(language, script, region, variants, extensions)
70 elsif klass == Rfc
71 exts = []
72 @extensions.to_a.sort.each do |k, v|
73 exts << "k-#{k[0,8]}-#{v[0,8]}"
74 end
75
76 klass.new(language, script, region, variants, exts)
77 else
78 super
79 end
80 end
81
82 # Returns the language tag.
83 # (e.g.) "ja_Hira_JP_VARIANT1_VARIANT2@foo1=var1;foo2=var2"
84 #
85 # This is used in internal only. Use to_s instead.
86 def to_string
87 s = super
88 if @extensions.size > 0
89 s << "@" << @extensions.to_a.sort.map{|k, v| "#{k}=#{v}"}.join(";")
90 end
91 s
92 end
93 end
94 end
95 end
+0
-124
vendor/locale/tag/common.rb less more
0 =begin
1 locale/tag/common.rb - Locale::Tag::Common
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/simple'
10
11 module Locale
12 module Tag
13 # Common Language tag class for Ruby.
14 # Java and MS Windows use this format.
15 #
16 # * ja (language: RFC4646)
17 # * ja_JP (country: RFC4646(2 alpha or 3 digit))
18 # * ja-JP
19 # * ja_Hira_JP (script: 4 characters)
20 # * ja-Hira-JP
21 # * ja_Hira_JP_MOBILE (variants: more than 2 characters or 3 digit)
22 # * ja_Hira_JP_MOBILE_IPHONE (2 variants example)
23 #
24 class Common < Simple
25 LANGUAGE = "(#{ALPHA}{2,3}|#{ALPHA}{4}|#{ALPHA}{5,8})" #RFC4646 (ISO639/reserved/registered)
26 SCRIPT = "(#{ALPHA}{4})"
27 VARIANT = "(#{ALPHANUM}{3,}|#{DIGIT}#{ALPHANUM}{3})" #RFC3066 compatible
28
29 TAG_RE = /\A#{LANGUAGE}(?:[-_]#{SCRIPT})?
30 (?:[-_]#{REGION})?((?:[-_]#{VARIANT})*)\Z/ix
31
32 attr_reader :script, :variants
33
34 class << self
35 # Parse the language tag and return the new Locale::Tag::Common.
36 def parse(tag)
37 if tag =~ /\APOSIX\Z/ # This is the special case of POSIX locale but match this regexp.
38 nil
39 elsif tag =~ TAG_RE
40 lang, script, region, subtag = $1, $2, $3, $4
41 variants = subtag.scan(/(^|[-_])#{VARIANT}(?=([-_]|$))/i).collect{|v| v[1]}
42
43 ret = self.new(lang, script, region, variants)
44 ret.tag = tag
45 ret
46 else
47 nil
48 end
49 end
50 end
51
52 # Create a Locale::Tag::Common.
53 def initialize(language, script = nil, region = nil, variants = [])
54 @script, @variants = script, variants
55 @script = @script.capitalize if @script
56 super(language, region)
57 end
58
59 # Set the script (with capitalize)
60 def script=(val)
61 @script = val
62 @script = @script.capitalize if @script
63 @script
64 end
65
66 # Set the variants as an Array.
67 def variants=(val)
68 @variants = val
69 end
70
71 # Returns an Array of tag-candidates order by priority.
72 # Use Locale.candidates instead of this method.
73 #
74 # Locale::Tag::Rfc, Cldr don't have their own candidates,
75 # because it's meaningless to compare the extensions, privateuse, etc.
76 def candidates
77 [self.class.new(language, script, region, variants), #ja-Kana-JP-FOO
78 self.class.new(language, script, region), #ja-Kana-JP
79 self.class.new(language, nil, region, variants), #ja-JP-FOO
80 self.class.new(language, nil, region), #ja-JP
81 self.class.new(language, script, nil, variants), #ja-Kana-FOO
82 self.class.new(language, script), #ja-Kana
83 self.class.new(language, nil, nil, variants), #ja-FOO
84 self.class.new(language)] #ja
85 end
86
87 private
88 def convert_to(klass) #:nodoc:
89 if klass == Simple
90 super
91 elsif klass == Posix
92 if variants.size > 0
93 var = variants.join("-")
94 else
95 var = nil
96 end
97 klass.new(language, region, nil, var)
98 elsif klass == Cldr
99 klass.new(language, script, region, variants.map{|v| v.upcase})
100 else
101 klass.new(language, script, region, variants)
102 end
103 end
104
105 # Returns the common language tag with "_".
106 # <language>_<Script>_<REGION>_VARIANTS1_VARIANTS2
107 # (e.g.) "ja_Hira_JP_VARIANTS1_VARIANTS2"
108 #
109 # This is used in internal only. Use to_s instead.
110 def to_string
111 s = @language.dup
112
113 s << "_" << @script if @script
114 s << "_" << @region if @region
115
116 @variants.each do |v|
117 s << "_#{v}"
118 end
119 s
120 end
121 end
122 end
123 end
+0
-38
vendor/locale/tag/irregular.rb less more
0 =begin
1 locale/tag/irregular.rb - Locale::Tag::Irregular
2
3 Copyright (C) 2008 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 $Id: irregular.rb 27 2008-12-03 15:06:50Z mutoh $
9 =end
10
11 require 'locale/tag/simple'
12
13 module Locale
14
15 module Tag
16 # Broken tag class.
17 class Irregular < Simple
18
19 def initialize(tag)
20 tag = "en" if tag == nil or tag == ""
21 super(tag.to_s)
22 @tag = tag
23 end
24
25 # Returns an Array of tag-candidates order by priority.
26 def candidates
27 [Irregular.new(tag)]
28 end
29
30 # Conver to the klass(the class of Language::Tag)
31 private
32 def convert_to(klass)
33 klass.new(tag)
34 end
35 end
36 end
37 end
+0
-95
vendor/locale/tag/posix.rb less more
0 =begin
1 locale/tag/posix.rb - Locale::Tag::Posix
2
3 Copyright (C) 2008 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 $Id: posix.rb 27 2008-12-03 15:06:50Z mutoh $
9 =end
10
11 module Locale
12 module Tag
13
14 # Locale tag class for POSIX locale
15 # * ja
16 # * ja_JP
17 # * ja_JP.UTF-8
18 # * ja_JP.UTF-8@Osaka
19 # * C/POSIX (-> en_US)
20 class Posix < Simple
21 LANGUAGE = "([a-z]{2,})"
22 TAG_RE = /\A#{LANGUAGE}(?:_#{REGION})?(?:\.([^@]+))?(?:@(.*))?\Z/i
23
24 attr_reader :charset, :modifier
25
26 def initialize(language, region = nil, charset = nil, modifier = nil)
27 @charset, @modifier = charset, modifier
28 super(language, region)
29 end
30
31 def self.parse(tag)
32 if tag =~ /^(C|POSIX)$/
33 ret = self.new("en", "US")
34 ret.tag = tag
35 ret
36 elsif tag =~ TAG_RE
37 ret = self.new($1, $2, $3, $4)
38 ret.tag = tag
39 ret
40 else
41 nil
42 end
43 end
44
45 # Returns the language tag.
46 # <language>_<COUNTRY>.<CHARSET>@<MODIFIER>
47 # (e.g.) "ja_JP.EUC-JP@Modifier"
48 def to_s
49 s = @language.dup
50 s << "_#{@region}" if @region
51 s << ".#{@charset}" if @charset
52 s << "@#{@modifier}" if @modifier
53 s
54 end
55
56 # Set the charset.
57 def charset=(val)
58 @charset = val
59 end
60
61 # Set the modifier as a String
62 def modifier=(val)
63 @modifier = val
64 end
65
66 # Returns an Array of tag-candidates order by priority.
67 # Use Locale.candidates instead of this method.
68 def candidates
69 [self.class.new(language, region, charset, modifier), #ja_JP.UTF-8@Modifier
70 self.class.new(language, region, charset), #ja_JP.UTF-8
71 self.class.new(language, region, nil, modifier), #ja_JP@Modifier
72 self.class.new(language, region, nil, nil), #ja_JP@Modifier
73 self.class.new(language, nil, charset, modifier), #ja.UTF-8@Modifier
74 self.class.new(language, nil, charset), #ja.UTF-8
75 self.class.new(language, nil, nil, modifier), #ja@Modifier
76 self.class.new(language)] #ja
77 end
78
79 # A modifier is converted to a variant.
80 # If the modifier is less than 5 characters, it is not canonical value.
81 private
82 def convert_to(klass)
83 if klass == Simple
84 super
85 elsif klass == Posix
86 klass.new(language, region, charset, modifier)
87 else
88 klass.new(language, nil, region, modifier ? [modifier] : [])
89 end
90 end
91
92 end
93 end
94 end
+0
-108
vendor/locale/tag/rfc.rb less more
0 =begin
1 locale/tag/rfc.rb - Locale::Tag::Rfc
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/common'
10
11 module Locale
12 module Tag
13
14 # Language tag class for RFC4646(BCP47).
15 class Rfc < Common
16 SINGLETON = '[a-wyz0-9]'
17 VARIANT = "(#{ALPHANUM}{5,8}|#{DIGIT}#{ALPHANUM}{3})"
18 EXTENSION = "(#{SINGLETON}(?:-#{ALPHANUM}{2,8})+)"
19 PRIVATEUSE = "(x(?:-#{ALPHANUM}{1,8})+)"
20 GRANDFATHERED = "#{ALPHA}{1,3}(?:-#{ALPHANUM}{2,8}){1,2}"
21
22 TAG_RE = /\A#{LANGUAGE}(?:-#{SCRIPT})?
23 (?:-#{REGION})?((?:-#{VARIANT})*
24 (?:-#{EXTENSION})*(?:-#{PRIVATEUSE})?)\Z/ix
25
26 attr_reader :extensions, :privateuse
27
28 class << self
29 # Parse the language tag and return the new Locale::Tag::Rfc.
30 def parse(tag)
31 if tag =~ /\APOSIX\Z/ # This is the special case of POSIX locale but match this regexp.
32 nil
33 elsif tag =~ TAG_RE
34 lang, script, region, subtag = $1, $2, $3, $4
35 extensions = []
36 variants = []
37 if subtag =~ /#{PRIVATEUSE}/
38 subtag, privateuse = $`, $1
39 # Private use for CLDR.
40 if /x-ldml(.*)/ =~ privateuse
41 p_subtag = $1
42 extensions = p_subtag.scan(/(^|-)#{EXTENSION}/i).collect{|v| p_subtag.sub!(v[1], ""); v[1]}
43 variants = p_subtag.scan(/(^|-)#{VARIANT}(?=(-|$))/i).collect{|v| v[1]}
44 end
45 end
46 extensions += subtag.scan(/(^|-)#{EXTENSION}/i).collect{|v| subtag.sub!(v[1], ""); v[1]}
47 variants += subtag.scan(/(^|-)#{VARIANT}(?=(-|$))/i).collect{|v| v[1]}
48
49 ret = self.new(lang, script, region, variants, extensions, privateuse)
50 ret.tag = tag
51 ret
52 else
53 nil
54 end
55 end
56 end
57
58 def initialize(language, script = nil, region = nil, variants = [],
59 extensions = [], privateuse = nil)
60 @extensions, @privateuse = extensions, privateuse
61 super(language, script, region, variants)
62 end
63
64 # Sets the extensions as an Array.
65 def extensions=(val)
66 @extensions = val
67 end
68
69 # Sets the privateuse as a String
70 def privateuse=(val)
71 @privateuse = val
72 end
73
74 private
75 def convert_to(klass)
76 if klass == Rfc
77 klass.new(language, script, region, variants, extensions, privateuse)
78 elsif klass == Cldr
79 exts = {}
80 extensions.sort.each do |v|
81 if v =~ /^k-(#{ALPHANUM}{2,})-(.*)$/i
82 exts[$1] = $2
83 end
84 end
85 klass.new(language, script, region, variants, exts)
86 else
87 super
88 end
89 end
90
91 # Returns the language tag
92 # <language>-<Script>-<REGION>-<variants>-<extensions>-<PRIVATEUSE>
93 # (e.g.) "ja-Hira-JP-variant"
94 #
95 # This is used in internal only. Use to_s instead.
96 def to_string
97 s = super.gsub(/_/, "-")
98 @extensions.sort.each do |v|
99 s << "-#{v}"
100 end
101 s << "-#{@privateuse}" if @privateuse
102 s
103 end
104
105 end
106 end
107 end
+0
-146
vendor/locale/tag/simple.rb less more
0 =begin
1 locale/tag/simple.rb - Locale::Tag::Simple
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 module Locale
10 module Tag
11 # Abstract language tag class.
12 # This class has <language>, <region> which
13 # all of language tag specifications have.
14 #
15 # * ja (language: ISO 639 (2 or 3 alpha))
16 # * ja_JP (country: RFC4646 (ISO3166/UN M.49) (2 alpha or 3 digit)
17 # * ja-JP
18 # * ja-392
19 class Simple
20 ALPHA = '[a-z]'
21 DIGIT = '[0-9]'
22 ALPHANUM = "[a-zA-Z0-9]"
23
24 LANGUAGE = "(#{ALPHA}{2,3})" # ISO 639
25 REGION = "(#{ALPHA}{2}|#{DIGIT}{3})" # RFC4646 (ISO3166/UN M.49)
26
27 TAG_RE = /\A#{LANGUAGE}(?:[_-]#{REGION})?\Z/i
28
29 attr_reader :language, :region
30
31 # tag is set when .parse method is called.
32 # This value is used when the program want to know the original
33 # String.
34 attr_accessor :tag
35
36 # call-seq:
37 # to_common
38 # to_posix
39 # to_rfc
40 # to_cldr
41 #
42 # Convert to each tag classes.
43 [:simple, :common, :posix, :rfc, :cldr].each do |name|
44 class_eval <<-EOS
45 def to_#{name}
46 convert_to(#{name.to_s.capitalize})
47 end
48 EOS
49 end
50
51 class << self
52 # Parse the language tag and return the new Locale::Tag::Simple.
53 def parse(tag)
54 if tag =~ TAG_RE
55 ret = self.new($1, $2)
56 ret.tag = tag
57 ret
58 else
59 nil
60 end
61 end
62 end
63
64 # Create a Locale::Tag::Simple
65 def initialize(language, region = nil)
66 raise "language can't be nil." unless language
67 @language, @region = language, region
68 @language = @language.downcase if @language
69 @region = @region.upcase if @region
70 end
71
72 # Returns the language tag as the String.
73 # <language>_<REGION>
74 # (e.g.) "ja_JP"
75 def to_s
76 to_string
77 end
78
79 def to_str #:nodoc:
80 to_s
81 end
82
83 def <=>(other)
84 self.to_s <=> other.to_s
85 end
86
87 def ==(other) #:nodoc:
88 other != nil and hash == other.hash
89 end
90
91 def eql?(other) #:nodoc:
92 self.==(other)
93 end
94
95 def hash #:nodoc:
96 "#{self.class}:#{to_s}".hash
97 end
98
99 def inspect #:nodoc:
100 %Q[#<#{self.class}: #{to_s}>]
101 end
102
103 # For backward compatibility.
104 def country; region end
105
106 # Set the language (with downcase)
107 def language=(val)
108 @language = val
109 @language = @language.downcase if @language
110 @language
111 end
112
113 # Set the region (with upcase)
114 def region=(val)
115 @region = val
116 @region = @region.upcase if @region
117 @region
118 end
119
120 # Returns an Array of tag-candidates order by priority.
121 # Use Locale.candidates instead of this method.
122 def candidates
123 [self.class.new(language, region), self.class.new(language)]
124 end
125
126 # Convert to the klass(the class of Language::Tag)
127 private
128 def convert_to(klass) #:nodoc:
129 if klass == Simple || klass == Posix
130 klass.new(language, region)
131 else
132 klass.new(language, nil, region)
133 end
134 end
135
136 # Return simple language tag which format is"<lanuguage>_<REGION>".
137 # This is to use internal only. Use to_s instead.
138 def to_string
139 s = @language.dup
140 s << "_" << @region if @region
141 s
142 end
143 end
144 end
145 end
+0
-36
vendor/locale/tag.rb less more
0 =begin
1 tag.rb - Locale::Tag module
2
3 Copyright (C) 2008,2009 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7 =end
8
9 require 'locale/tag/simple'
10 require 'locale/tag/irregular'
11 require 'locale/tag/common'
12 require 'locale/tag/rfc'
13 require 'locale/tag/cldr'
14 require 'locale/tag/posix'
15
16 module Locale
17
18 # Language tag / locale identifiers.
19 module Tag
20 module_function
21 # Parse a language tag/locale name and return Locale::Tag
22 # object.
23 # * tag: a tag as a String. e.g.) ja-Hira-JP
24 # * Returns: a Locale::Tag subclass.
25 def parse(tag)
26 # Common is not used here.
27 [Simple, Common, Rfc, Cldr, Posix].each do |parser|
28 ret = parser.parse(tag)
29 return ret if ret
30 end
31 Locale::Tag::Irregular.new(tag)
32 end
33 end
34 end
35
+0
-102
vendor/locale/taglist.rb less more
0 =begin
1 taglist.rb - Locale module
2
3 Copyright (C) 2008 Masao Mutoh
4
5 You may redistribute it and/or modify it under the same
6 license terms as Ruby.
7
8 $Id: taglist.rb 27 2008-12-03 15:06:50Z mutoh $
9 =end
10
11 module Locale
12 # This provides the subclass of Array which behaves like
13 # the first(top priority) Locale::Tag object.
14 # "Locale.current.language" is same with "Locale.current[0].language".
15 #
16 # Locale.current returns an Array of Tag(s) now.
17 # But the old Locale.current(Ruby-GetText) and Locale.get
18 # returns Locale::Object (similier with Locale::Tag::Posix).
19 # This is the class for backward compatibility.
20 #
21 # It is recommanded to use Locale.current[0] or
22 # Locale.candidates to find the current locale instead
23 # of this function.
24 #
25 class TagList < Array
26 # Returns the top priority language. (simple)
27 def language
28 self[0].language
29 end
30 # Returns the top priority region/country. (simple)
31 def country
32 self[0].region
33 end
34 # Returns the top priority region/country. (simple)
35 def region
36 self[0].region
37 end
38 # Returns the top priority script. (common)
39 def script
40 self[0].script
41 end
42 # Returns the top priority charset. (posix)
43 def charset
44 top_priority_charset = nil
45 first_tag = self[0]
46 if first_tag.respond_to?(:charset)
47 top_priority_charset = first_tag.charset
48 end
49 top_priority_charset ||= ::Locale.driver_module.charset
50 top_priority_charset
51 end
52
53 # Returns the top priority modifier. (posix)
54 def modifier
55 (self[0].respond_to? :modifier) ? self[0].modifier : nil
56 end
57
58 # Returns the top priority variants.(common, rfc, cldr)
59 def variants
60 (self[0].respond_to? :variants) ? self[0].variants : nil
61 end
62
63 # Returns the top priority extensions.(common, rfc, cldr)
64 def extensions
65 (self[0].respond_to? :extensions) ? self[0].extensions : nil
66 end
67
68 # Returns the top priority privateuse(rfc)
69 def privateuse
70 (self[0].respond_to? :privateuse) ? self[0].privateuse : nil
71 end
72
73 def to_str
74 self[0].to_str
75 end
76
77 def to_s
78 self[0].to_s
79 end
80
81 def to_common
82 self[0].to_common
83 end
84
85 def to_simple
86 self[0].to_simple
87 end
88
89 def to_rfc
90 self[0].to_rfc
91 end
92
93 def to_cldr
94 self[0].to_cldr
95 end
96
97 def to_posix
98 self[0].to_posix
99 end
100 end
101 end
+0
-13
vendor/locale/version.rb less more
0 =begin
1 version - version information of Ruby-Locale
2
3 Copyright (C) 2008 Masao Mutoh
4 Copyright (C) 2013-2015 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.2"
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
-12
vendor/memoist/core_ext/singleton_class.rb less more
0 # frozen_string_literal: true
1
2 module Kernel
3 # Returns the object's singleton class.
4 unless respond_to?(:singleton_class)
5 def singleton_class
6 class << self
7 self
8 end
9 end
10 end # exists in 1.9.2
11 end
+0
-5
vendor/memoist/version.rb less more
0 # frozen_string_literal: true
1
2 module Memoist
3 VERSION = '0.16.0'.freeze
4 end
+0
-239
vendor/memoist.rb less more
0 # frozen_string_literal: true
1
2 require 'memoist/version'
3 require 'memoist/core_ext/singleton_class'
4
5 module Memoist
6 def self.extended(extender)
7 Memoist.memoist_eval(extender) do
8 unless singleton_class.method_defined?(:memoized_methods)
9 def self.memoized_methods
10 @_memoized_methods ||= []
11 end
12 end
13 end
14 end
15
16 def self.memoized_ivar_for(method_name, identifier = nil)
17 "@#{memoized_prefix(identifier)}_#{escape_punctuation(method_name)}"
18 end
19
20 def self.unmemoized_method_for(method_name, identifier = nil)
21 "#{unmemoized_prefix(identifier)}_#{method_name}".to_sym
22 end
23
24 def self.memoized_prefix(identifier = nil)
25 if identifier
26 "_memoized_#{identifier}"
27 else
28 '_memoized'.freeze
29 end
30 end
31
32 def self.unmemoized_prefix(identifier = nil)
33 if identifier
34 "_unmemoized_#{identifier}"
35 else
36 '_unmemoized'.freeze
37 end
38 end
39
40 def self.escape_punctuation(string)
41 string = string.is_a?(String) ? string.dup : string.to_s
42
43 return string unless string.end_with?('?'.freeze, '!'.freeze)
44
45 # A String can't end in both ? and !
46 if string.sub!(/\?\Z/, '_query'.freeze)
47 else
48 string.sub!(/!\Z/, '_bang'.freeze)
49 end
50 string
51 end
52
53 def self.memoist_eval(klass, *args, &block)
54 if klass.respond_to?(:class_eval)
55 klass.class_eval(*args, &block)
56 else
57 klass.singleton_class.class_eval(*args, &block)
58 end
59 end
60
61 def self.extract_reload!(method, args)
62 if args.length == method.arity.abs + 1 && (args.last == true || args.last == :reload)
63 reload = args.pop
64 end
65 reload
66 end
67
68 module InstanceMethods
69 def memoize_all
70 prime_cache
71 end
72
73 def unmemoize_all
74 flush_cache
75 end
76
77 def memoized_structs(names)
78 ref_obj = self.class.respond_to?(:class_eval) ? singleton_class : self
79 structs = ref_obj.all_memoized_structs
80 return structs if names.empty?
81
82 structs.select { |s| names.include?(s.memoized_method) }
83 end
84
85 def prime_cache(*method_names)
86 memoized_structs(method_names).each do |struct|
87 if struct.arity == 0
88 __send__(struct.memoized_method)
89 else
90 instance_variable_set(struct.ivar, {})
91 end
92 end
93 end
94
95 def flush_cache(*method_names)
96 memoized_structs(method_names).each do |struct|
97 remove_instance_variable(struct.ivar) if instance_variable_defined?(struct.ivar)
98 end
99 end
100 end
101
102 MemoizedMethod = Struct.new(:memoized_method, :ivar, :arity)
103
104 def all_memoized_structs
105 @all_memoized_structs ||= begin
106 structs = memoized_methods.dup
107
108 # Collect the memoized_methods of ancestors in ancestor order
109 # unless we already have it since self or parents could be overriding
110 # an ancestor method.
111 ancestors.grep(Memoist).each do |ancestor|
112 ancestor.memoized_methods.each do |m|
113 structs << m unless structs.any? { |am| am.memoized_method == m.memoized_method }
114 end
115 end
116 structs
117 end
118 end
119
120 def clear_structs
121 @all_memoized_structs = nil
122 end
123
124 def memoize(*method_names)
125 identifier = method_names.pop[:identifier] if method_names.last.is_a?(Hash)
126
127 method_names.each do |method_name|
128 unmemoized_method = Memoist.unmemoized_method_for(method_name, identifier)
129 memoized_ivar = Memoist.memoized_ivar_for(method_name, identifier)
130
131 Memoist.memoist_eval(self) do
132 include InstanceMethods
133
134 if method_defined?(unmemoized_method)
135 warn "Already memoized #{method_name}"
136 return
137 end
138 alias_method unmemoized_method, method_name
139
140 mm = MemoizedMethod.new(method_name, memoized_ivar, instance_method(method_name).arity)
141 memoized_methods << mm
142 if mm.arity == 0
143
144 # define a method like this;
145
146 # def mime_type(reload=true)
147 # skip_cache = reload || !instance_variable_defined?("@_memoized_mime_type")
148 # set_cache = skip_cache && !frozen?
149 #
150 # if skip_cache
151 # value = _unmemoized_mime_type
152 # else
153 # value = @_memoized_mime_type
154 # end
155 #
156 # if set_cache
157 # @_memoized_mime_type = value
158 # end
159 #
160 # value
161 # end
162
163 module_eval <<-EOS, __FILE__, __LINE__ + 1
164 def #{method_name}(reload = false)
165 skip_cache = reload || !instance_variable_defined?("#{memoized_ivar}")
166 set_cache = skip_cache && !frozen?
167
168 if skip_cache
169 value = #{unmemoized_method}
170 else
171 value = #{memoized_ivar}
172 end
173
174 if set_cache
175 #{memoized_ivar} = value
176 end
177
178 value
179 end
180 EOS
181 else
182
183 # define a method like this;
184
185 # def mime_type(*args)
186 # reload = Memoist.extract_reload!(method(:_unmemoized_mime_type), args)
187 #
188 # skip_cache = reload || !memoized_with_args?(:mime_type, args)
189 # set_cache = skip_cache && !frozen
190 #
191 # if skip_cache
192 # value = _unmemoized_mime_type(*args)
193 # else
194 # value = @_memoized_mime_type[args]
195 # end
196 #
197 # if set_cache
198 # @_memoized_mime_type ||= {}
199 # @_memoized_mime_type[args] = value
200 # end
201 #
202 # value
203 # end
204
205 module_eval <<-EOS, __FILE__, __LINE__ + 1
206 def #{method_name}(*args)
207 reload = Memoist.extract_reload!(method(#{unmemoized_method.inspect}), args)
208
209 skip_cache = reload || !(instance_variable_defined?(#{memoized_ivar.inspect}) && #{memoized_ivar} && #{memoized_ivar}.has_key?(args))
210 set_cache = skip_cache && !frozen?
211
212 if skip_cache
213 value = #{unmemoized_method}(*args)
214 else
215 value = #{memoized_ivar}[args]
216 end
217
218 if set_cache
219 #{memoized_ivar} ||= {}
220 #{memoized_ivar}[args] = value
221 end
222
223 value
224 end
225 EOS
226 end
227
228 if private_method_defined?(unmemoized_method)
229 private method_name
230 elsif protected_method_defined?(unmemoized_method)
231 protected method_name
232 end
233 end
234 end
235 # return a chainable method_name symbol if we can
236 method_names.length == 1 ? method_names.first : method_names
237 end
238 end
+0
-71
vendor/oauth/cli/authorize_command.rb less more
0 class OAuth::CLI
1 class AuthorizeCommand < BaseCommand
2
3 def required_options
4 [:uri]
5 end
6
7 def _run
8 request_token = get_request_token
9
10 if request_token.callback_confirmed?
11 puts "Server appears to support OAuth 1.0a; enabling support."
12 options[:version] = "1.0a"
13 end
14
15 puts "Please visit this url to authorize:"
16 puts request_token.authorize_url
17
18 # parameters for OAuth 1.0a
19 oauth_verifier = ask_user_for_verifier
20
21 verbosely_get_access_token(request_token, oauth_verifier)
22 end
23
24 def get_request_token
25 consumer = get_consumer
26 scope_options = options[:scope] ? { "scope" => options[:scope] } : {}
27 consumer.get_request_token({ :oauth_callback => options[:oauth_callback] }, scope_options)
28 rescue OAuth::Unauthorized => e
29 alert "A problem occurred while attempting to authorize:"
30 alert e
31 alert e.request.body
32 end
33
34 def get_consumer
35 OAuth::Consumer.new \
36 options[:oauth_consumer_key],
37 options[:oauth_consumer_secret],
38 :access_token_url => options[:access_token_url],
39 :authorize_url => options[:authorize_url],
40 :request_token_url => options[:request_token_url],
41 :scheme => options[:scheme],
42 :http_method => options[:method].to_s.downcase.to_sym
43 end
44
45
46 def ask_user_for_verifier
47 if options[:version] == "1.0a"
48 puts "Please enter the verification code provided by the SP (oauth_verifier):"
49 @stdin.gets.chomp
50 else
51 puts "Press return to continue..."
52 @stdin.gets
53 nil
54 end
55 end
56
57 def verbosely_get_access_token(request_token, oauth_verifier)
58 access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
59
60 puts "Response:"
61 access_token.params.each do |k,v|
62 puts " #{k}: #{v}" unless k.is_a?(Symbol)
63 end
64 rescue OAuth::Unauthorized => e
65 alert "A problem occurred while attempting to obtain an access token:"
66 alert e
67 alert e.request.body
68 end
69 end
70 end
+0
-208
vendor/oauth/cli/base_command.rb less more
0 class OAuth::CLI
1 class BaseCommand
2 def initialize(stdout, stdin, stderr, arguments)
3 @stdout, @stdin, @stderr = stdout, stdin, stderr
4
5 @options = {}
6 option_parser.parse!(arguments)
7 end
8
9 def run
10 missing = required_options - options.keys
11 if missing.empty?
12 _run
13 else
14 show_missing(missing)
15 puts option_parser.help
16 end
17 end
18
19 def required_options
20 []
21 end
22
23 protected
24
25 attr_reader :options
26
27 def show_missing(array)
28 array = array.map { |s| "--#{s}" }.join(' ')
29 OAuth::CLI.puts_red "Options missing to OAuth CLI: #{array}"
30 end
31
32 def xmpp?
33 options[:xmpp]
34 end
35
36 def verbose?
37 options[:verbose]
38 end
39
40 def puts(string=nil)
41 @stdout.puts(string)
42 end
43
44 def alert(string=nil)
45 @stderr.puts(string)
46 end
47
48 def parameters
49 @parameters ||= begin
50 escaped_pairs = options[:params].collect do |pair|
51 if pair =~ /:/
52 Hash[*pair.split(":", 2)].collect do |k,v|
53 [CGI.escape(k.strip), CGI.escape(v.strip)] * "="
54 end
55 else
56 pair
57 end
58 end
59
60 querystring = escaped_pairs * "&"
61 cli_params = CGI.parse(querystring)
62
63 {
64 "oauth_consumer_key" => options[:oauth_consumer_key],
65 "oauth_nonce" => options[:oauth_nonce],
66 "oauth_timestamp" => options[:oauth_timestamp],
67 "oauth_token" => options[:oauth_token],
68 "oauth_signature_method" => options[:oauth_signature_method],
69 "oauth_version" => options[:oauth_version]
70 }.reject { |_k,v| v.nil? || v == "" }.merge(cli_params)
71 end
72 end
73
74 def option_parser
75 @option_parser ||= OptionParser.new do |opts|
76 opts.banner = "Usage: oauth <command> [ARGS]"
77
78 _option_parser_defaults
79 _option_parser_common(opts)
80 _option_parser_sign_and_query(opts)
81 _option_parser_authorization(opts)
82 end
83 end
84
85 def _option_parser_defaults
86 options[:oauth_nonce] = OAuth::Helper.generate_key
87 options[:oauth_signature_method] = "HMAC-SHA1"
88 options[:oauth_timestamp] = OAuth::Helper.generate_timestamp
89 options[:oauth_version] = "1.0"
90 options[:method] = :post
91 options[:params] = []
92 options[:scheme] = :header
93 options[:version] = "1.0"
94 end
95
96 def _option_parser_common(opts)
97 ## Common Options
98
99 opts.on("-B", "--body", "Use the request body for OAuth parameters.") do
100 options[:scheme] = :body
101 end
102
103 opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v|
104 options[:oauth_consumer_key] = v
105 end
106
107 opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v|
108 options[:oauth_consumer_secret] = v
109 end
110
111 opts.on("-H", "--header", "Use the 'Authorization' header for OAuth parameters (default).") do
112 options[:scheme] = :header
113 end
114
115 opts.on("-Q", "--query-string", "Use the query string for OAuth parameters.") do
116 options[:scheme] = :query_string
117 end
118
119 opts.on("-O", "--options FILE", "Read options from a file") do |v|
120 arguments = open(v).readlines.map { |l| l.chomp.split(" ") }.flatten
121 options2 = parse_options(arguments)
122 options.merge!(options2)
123 end
124 end
125
126 def _option_parser_sign_and_query(opts)
127 opts.separator("\n options for signing and querying")
128
129 opts.on("--method METHOD", "Specifies the method (e.g. GET) to use when signing.") do |v|
130 options[:method] = v
131 end
132
133 opts.on("--nonce NONCE", "Specifies the nonce to use.") do |v|
134 options[:oauth_nonce] = v
135 end
136
137 opts.on("--parameters PARAMS", "Specifies the parameters to use when signing.") do |v|
138 options[:params] << v
139 end
140
141 opts.on("--signature-method METHOD", "Specifies the signature method to use; defaults to HMAC-SHA1.") do |v|
142 options[:oauth_signature_method] = v
143 end
144
145 opts.on("--token TOKEN", "Specifies the token to use.") do |v|
146 options[:oauth_token] = v
147 end
148
149 opts.on("--secret SECRET", "Specifies the token secret to use.") do |v|
150 options[:oauth_token_secret] = v
151 end
152
153 opts.on("--timestamp TIMESTAMP", "Specifies the timestamp to use.") do |v|
154 options[:oauth_timestamp] = v
155 end
156
157 opts.on("--realm REALM", "Specifies the realm to use.") do |v|
158 options[:realm] = v
159 end
160
161 opts.on("--uri URI", "Specifies the URI to use when signing.") do |v|
162 options[:uri] = v
163 end
164
165 opts.on("--version [VERSION]", "Specifies the OAuth version to use.") do |v|
166 options[:oauth_version] = v
167 end
168
169 opts.on("--no-version", "Omit oauth_version.") do
170 options[:oauth_version] = nil
171 end
172
173 opts.on("--xmpp", "Generate XMPP stanzas.") do
174 options[:xmpp] = true
175 options[:method] ||= "iq"
176 end
177
178 opts.on("-v", "--verbose", "Be verbose.") do
179 options[:verbose] = true
180 end
181 end
182
183 def _option_parser_authorization(opts)
184 opts.separator("\n options for authorization")
185
186 opts.on("--access-token-url URL", "Specifies the access token URL.") do |v|
187 options[:access_token_url] = v
188 end
189
190 opts.on("--authorize-url URL", "Specifies the authorization URL.") do |v|
191 options[:authorize_url] = v
192 end
193
194 opts.on("--callback-url URL", "Specifies a callback URL.") do |v|
195 options[:oauth_callback] = v
196 end
197
198 opts.on("--request-token-url URL", "Specifies the request token URL.") do |v|
199 options[:request_token_url] = v
200 end
201
202 opts.on("--scope SCOPE", "Specifies the scope (Google-specific).") do |v|
203 options[:scope] = v
204 end
205 end
206 end
207 end
+0
-22
vendor/oauth/cli/help_command.rb less more
0 class OAuth::CLI
1 class HelpCommand < BaseCommand
2 def run
3 puts <<-EOT
4 Usage: oauth COMMAND [ARGS]
5
6 Available oauth commands are:
7 a, authorize Obtain an access token and secret for a user
8 q, query Query a protected resource
9 s, sign Generate an OAuth signature
10
11 In addition to those, there are:
12 v, version Displays the current version of the library (or --version, -v)
13 h, help Displays this help (or --help, -h)
14
15 Tip: All commands can be run without args for specific help.
16
17
18 EOT
19 end
20 end
21 end
+0
-25
vendor/oauth/cli/query_command.rb less more
0 class OAuth::CLI
1 class QueryCommand < BaseCommand
2 extend OAuth::Helper
3
4 def required_options
5 [:oauth_consumer_key, :oauth_consumer_secret, :oauth_token, :oauth_token_secret]
6 end
7
8 def _run
9 consumer = OAuth::Consumer.new(options[:oauth_consumer_key], options[:oauth_consumer_secret], scheme: options[:scheme])
10
11 access_token = OAuth::AccessToken.new(consumer, options[:oauth_token], options[:oauth_token_secret])
12
13 # append params to the URL
14 uri = URI.parse(options[:uri])
15 params = parameters.map { |k,v| Array(v).map { |v2| "#{OAuth::Helper.escape(k)}=#{OAuth::Helper.escape(v2)}" } * "&" }
16 uri.query = [uri.query, *params].reject { |x| x.nil? } * "&"
17 puts uri.to_s
18
19 response = access_token.request(options[:method].to_s.downcase.to_sym, uri.to_s)
20 puts "#{response.code} #{response.message}"
21 puts response.body
22 end
23 end
24 end
+0
-81
vendor/oauth/cli/sign_command.rb less more
0 class OAuth::CLI
1 class SignCommand < BaseCommand
2
3 def required_options
4 [:oauth_consumer_key, :oauth_consumer_secret, :oauth_token, :oauth_token_secret]
5 end
6
7 def _run
8 request = OAuth::RequestProxy.proxy \
9 "method" => options[:method],
10 "uri" => options[:uri],
11 "parameters" => parameters
12
13 if verbose?
14 puts_verbose_parameters(request)
15 end
16
17 request.sign! \
18 :consumer_secret => options[:oauth_consumer_secret],
19 :token_secret => options[:oauth_token_secret]
20
21 if verbose?
22 puts_verbose_request(request)
23 else
24 puts request.oauth_signature
25 end
26 end
27
28 def puts_verbose_parameters(request)
29 puts "OAuth parameters:"
30 request.oauth_parameters.each do |k,v|
31 puts " " + [k, v] * ": "
32 end
33 puts
34
35 if request.non_oauth_parameters.any?
36 puts "Parameters:"
37 request.non_oauth_parameters.each do |k,v|
38 puts " " + [k, v] * ": "
39 end
40 puts
41 end
42 end
43
44 def puts_verbose_request(request)
45 puts "Method: #{request.method}"
46 puts "URI: #{request.uri}"
47 puts "Normalized params: #{request.normalized_parameters}" unless options[:xmpp]
48 puts "Signature base string: #{request.signature_base_string}"
49
50 if xmpp?
51 puts
52 puts "XMPP Stanza:"
53 puts xmpp_output(request)
54 puts
55 puts "Note: You may want to use bare JIDs in your URI."
56 puts
57 else
58 puts "OAuth Request URI: #{request.signed_uri}"
59 puts "Request URI: #{request.signed_uri(false)}"
60 puts "Authorization header: #{request.oauth_header(:realm => options[:realm])}"
61 end
62 puts "Signature: #{request.oauth_signature}"
63 puts "Escaped signature: #{OAuth::Helper.escape(request.oauth_signature)}"
64 end
65
66 def xmpp_output(request)
67 <<-EOS
68 <oauth xmlns='urn:xmpp:oauth:0'>
69 <oauth_consumer_key>#{request.oauth_consumer_key}</oauth_consumer_key>
70 <oauth_token>#{request.oauth_token}</oauth_token>
71 <oauth_signature_method>#{request.oauth_signature_method}</oauth_signature_method>
72 <oauth_signature>#{request.oauth_signature}</oauth_signature>
73 <oauth_timestamp>#{request.oauth_timestamp}</oauth_timestamp>
74 <oauth_nonce>#{request.oauth_nonce}</oauth_nonce>
75 <oauth_version>#{request.oauth_version}</oauth_version>
76 </oauth>
77 EOS
78 end
79 end
80 end
+0
-7
vendor/oauth/cli/version_command.rb less more
0 class OAuth::CLI
1 class VersionCommand < BaseCommand
2 def run
3 puts "OAuth Gem #{OAuth::VERSION}"
4 end
5 end
6 end
+0
-56
vendor/oauth/cli.rb less more
0 require 'optparse'
1 require 'oauth/cli/base_command'
2 require 'oauth/cli/help_command'
3 require 'oauth/cli/query_command'
4 require 'oauth/cli/authorize_command'
5 require 'oauth/cli/sign_command'
6 require 'oauth/cli/version_command'
7 require 'active_support/core_ext/string/inflections'
8
9 module OAuth
10 class CLI
11 def self.puts_red(string)
12 puts "\033[0;91m#{string}\033[0m"
13 end
14
15 ALIASES = {
16 'h' => 'help',
17 'v' => 'version',
18 'q' => 'query',
19 'a' => 'authorize',
20 's' => 'sign',
21 }
22
23 def initialize(stdout, stdin, stderr, command, arguments)
24 klass = get_command_class(parse_command(command))
25 @command = klass.new(stdout, stdin, stderr, arguments)
26 @help_command = HelpCommand.new(stdout, stdin, stderr, [])
27 end
28
29 def run
30 @command.run
31 end
32
33 private
34
35 def get_command_class(command)
36 Object.const_get("OAuth::CLI::#{command.camelize}Command")
37 end
38
39 def parse_command(command)
40 case command = command.to_s.downcase
41 when '--version', '-v'
42 'version'
43 when '--help', '-h', nil, ''
44 'help'
45 when *ALIASES.keys
46 ALIASES[command]
47 when *ALIASES.values
48 command
49 else
50 OAuth::CLI.puts_red "Command '#{command}' not found"
51 'help'
52 end
53 end
54 end
55 end
+0
-65
vendor/oauth/client/action_controller_request.rb less more
0 if defined? ActionDispatch
1 require 'oauth/request_proxy/rack_request'
2 require 'oauth/request_proxy/action_dispatch_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
-119
vendor/oauth/client/em_http.rb less more
0 require 'em-http'
1 require 'oauth/helper'
2 require 'oauth/request_proxy/em_http_request'
3
4 # Extensions for em-http so that we can use consumer.sign! with an EventMachine::HttpClient
5 # instance. This is purely syntactic sugar.
6 class EventMachine::HttpClient
7
8 attr_reader :oauth_helper
9
10 # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
11 # this may add a header, additional query string parameters, or additional POST body parameters.
12 # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
13 # header.
14 #
15 # * http - Configured Net::HTTP instance, ignored in this scenario except for getting host.
16 # * consumer - OAuth::Consumer instance
17 # * token - OAuth::Token instance
18 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
19 # +signature_method+, +nonce+, +timestamp+)
20 #
21 # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
22 #
23 # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1]
24 def oauth!(http, consumer = nil, token = nil, options = {})
25 options = { :request_uri => normalized_oauth_uri(http),
26 :consumer => consumer,
27 :token => token,
28 :scheme => 'header',
29 :signature_method => nil,
30 :nonce => nil,
31 :timestamp => nil }.merge(options)
32
33 @oauth_helper = OAuth::Client::Helper.new(self, options)
34 self.__send__(:"set_oauth_#{options[:scheme]}")
35 end
36
37 # Create a string suitable for signing for an HTTP request. This process involves parameter
38 # normalization as specified in the OAuth specification. The exact normalization also depends
39 # on the <tt>options[:scheme]</tt> being used so this must match what will be used for the request
40 # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
41 # header.
42 #
43 # * http - Configured Net::HTTP instance
44 # * consumer - OAuth::Consumer instance
45 # * token - OAuth::Token instance
46 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
47 # +signature_method+, +nonce+, +timestamp+)
48 #
49 # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
50 def signature_base_string(http, consumer = nil, token = nil, options = {})
51 options = { :request_uri => normalized_oauth_uri(http),
52 :consumer => consumer,
53 :token => token,
54 :scheme => 'header',
55 :signature_method => nil,
56 :nonce => nil,
57 :timestamp => nil }.merge(options)
58
59 OAuth::Client::Helper.new(self, options).signature_base_string
60 end
61
62 # This code was lifted from the em-http-request because it was removed from
63 # the gem June 19, 2010
64 # see: http://github.com/igrigorik/em-http-request/commit/d536fc17d56dbe55c487eab01e2ff9382a62598b
65 def normalize_uri
66 @normalized_uri ||= begin
67 uri = @uri.dup
68 encoded_query = encode_query(@uri, @options[:query])
69 path, query = encoded_query.split("?", 2)
70 uri.query = query unless encoded_query.empty?
71 uri.path = path
72 uri
73 end
74 end
75
76 protected
77
78 def combine_query(path, query, uri_query)
79 combined_query = if query.kind_of?(Hash)
80 query.map { |k, v| encode_param(k, v) }.join('&')
81 else
82 query.to_s
83 end
84 if !uri_query.to_s.empty?
85 combined_query = [combined_query, uri_query].reject {|part| part.empty?}.join("&")
86 end
87 combined_query.to_s.empty? ? path : "#{path}?#{combined_query}"
88 end
89
90 # Since we expect to get the host etc details from the http instance (...),
91 # we create a fake url here. Surely this is a horrible, horrible idea?
92 def normalized_oauth_uri(http)
93 uri = URI.parse(normalize_uri.path)
94 uri.host = http.address
95 uri.port = http.port
96
97 if http.respond_to?(:use_ssl?) && http.use_ssl?
98 uri.scheme = "https"
99 else
100 uri.scheme = "http"
101 end
102 uri.to_s
103 end
104
105 def set_oauth_header
106 headers = (self.options[:head] ||= {})
107 headers['Authorization'] = @oauth_helper.header
108 end
109
110 def set_oauth_body
111 raise NotImplementedError, 'please use the set_oauth_header method instead'
112 end
113
114 def set_oauth_query_string
115 raise NotImplementedError, 'please use the set_oauth_header method instead'
116 end
117
118 end
+0
-95
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 token_request?
59 @options[:token_request].eql?(true)
60 end
61
62 def hash_body
63 @options[:body_hash] = OAuth::Signature.body_hash(@request, :parameters => oauth_parameters)
64 end
65
66 def amend_user_agent_header(headers)
67 @oauth_ua_string ||= "OAuth gem v#{OAuth::VERSION}"
68 # Net::HTTP in 1.9 appends Ruby
69 if headers['User-Agent'] && headers['User-Agent'] != 'Ruby'
70 headers['User-Agent'] += " (#{@oauth_ua_string})"
71 else
72 headers['User-Agent'] = @oauth_ua_string
73 end
74 end
75
76 def header
77 parameters = oauth_parameters
78 parameters.merge!('oauth_signature' => signature(options.merge(:parameters => parameters)))
79
80 header_params_str = parameters.sort.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
81
82 realm = "realm=\"#{options[:realm]}\", " if options[:realm]
83 "OAuth #{realm}#{header_params_str}"
84 end
85
86 def parameters
87 OAuth::RequestProxy.proxy(@request).parameters
88 end
89
90 def parameters_with_oauth
91 oauth_parameters.merge(parameters)
92 end
93 end
94 end
+0
-121
vendor/oauth/client/net_http.rb less more
0 require 'oauth/helper'
1 require 'oauth/request_proxy/net_http'
2
3 class Net::HTTPGenericRequest
4 include OAuth::Helper
5
6 attr_reader :oauth_helper
7
8 # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
9 # this may add a header, additional query string parameters, or additional POST body parameters.
10 # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
11 # header.
12 #
13 # * http - Configured Net::HTTP instance
14 # * consumer - OAuth::Consumer instance
15 # * token - OAuth::Token instance
16 # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
17 # +signature_method+, +nonce+, +timestamp+)
18 #
19 # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
20 #
21 # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1],
22 # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html,
23 # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#when_to_include]
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 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.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 # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#when_to_include]
47 def signature_base_string(http, consumer = nil, token = nil, options = {})
48 helper_options = oauth_helper_options(http, consumer, token, options)
49 @oauth_helper = OAuth::Client::Helper.new(self, helper_options)
50 @oauth_helper.hash_body if oauth_body_hash_required?
51 @oauth_helper.signature_base_string
52 end
53
54 private
55
56 def oauth_helper_options(http, consumer, token, options)
57 { :request_uri => oauth_full_request_uri(http,options),
58 :consumer => consumer,
59 :token => token,
60 :scheme => 'header',
61 :signature_method => nil,
62 :nonce => nil,
63 :timestamp => nil }.merge(options)
64 end
65
66 def oauth_full_request_uri(http,options)
67 uri = URI.parse(self.path)
68 uri.host = http.address
69 uri.port = http.port
70
71 if options[:request_endpoint] && options[:site]
72 is_https = options[:site].match(%r(^https://))
73 uri.host = options[:site].gsub(%r(^https?://), '')
74 uri.port ||= is_https ? 443 : 80
75 end
76
77 if http.respond_to?(:use_ssl?) && http.use_ssl?
78 uri.scheme = "https"
79 else
80 uri.scheme = "http"
81 end
82
83 uri.to_s
84 end
85
86 def oauth_body_hash_required?
87 !@oauth_helper.token_request? && request_body_permitted? && !content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
88 end
89
90 def set_oauth_header
91 self['Authorization'] = @oauth_helper.header
92 end
93
94 # FIXME: if you're using a POST body and query string parameters, this method
95 # will move query string parameters into the body unexpectedly. This may
96 # cause problems with non-x-www-form-urlencoded bodies submitted to URLs
97 # containing query string params. If duplicate parameters are present in both
98 # places, all instances should be included when calculating the signature
99 # base string.
100
101 def set_oauth_body
102 self.set_form_data(@oauth_helper.stringify_keys(@oauth_helper.parameters_with_oauth))
103 params_with_sig = @oauth_helper.parameters.merge(:oauth_signature => @oauth_helper.signature)
104 self.set_form_data(@oauth_helper.stringify_keys(params_with_sig))
105 end
106
107 def set_oauth_query_string
108 oauth_params_str = @oauth_helper.oauth_parameters.map { |k,v| [escape(k), escape(v)] * "=" }.join("&")
109 uri = URI.parse(path)
110 if uri.query.to_s == ""
111 uri.query = oauth_params_str
112 else
113 uri.query = uri.query + "&" + oauth_params_str
114 end
115
116 @path = uri.to_s
117
118 @path << "&oauth_signature=#{escape(oauth_helper.signature)}"
119 end
120 end
+0
-4
vendor/oauth/client.rb less more
0 module OAuth
1 module Client
2 end
3 end
+0
-416
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(#{ENV['SSL_CERT_FILE']} /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt /usr/share/curl/curl-ca-bundle.crt)
11 CA_FILES.each do |ca_file|
12 if File.exist?(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 # Possible values:
46 #
47 # nil, false - no debug output
48 # true - uses $stdout
49 # some_value - uses some_value
50 :debug_output => nil,
51
52 :oauth_version => "1.0"
53 }
54
55 attr_accessor :options, :key, :secret
56 attr_writer :site, :http
57
58 # Create a new consumer instance by passing it a configuration hash:
59 #
60 # @consumer = OAuth::Consumer.new(key, secret, {
61 # :site => "http://term.ie",
62 # :scheme => :header,
63 # :http_method => :post,
64 # :request_token_path => "/oauth/example/request_token.php",
65 # :access_token_path => "/oauth/example/access_token.php",
66 # :authorize_path => "/oauth/example/authorize.php"
67 # })
68 #
69 # Start the process by requesting a token
70 #
71 # @request_token = @consumer.get_request_token
72 # session[:request_token] = @request_token
73 # redirect_to @request_token.authorize_url
74 #
75 # When user returns create an access_token
76 #
77 # @access_token = @request_token.get_access_token
78 # @photos=@access_token.get('/photos.xml')
79 #
80 def initialize(consumer_key, consumer_secret, options = {})
81 @key = consumer_key
82 @secret = consumer_secret
83
84 # ensure that keys are symbols
85 @options = @@default_options.merge(options.inject({}) do |opts, (key, value)|
86 opts[key.to_sym] = value
87 opts
88 end)
89 end
90
91 # The default http method
92 def http_method
93 @http_method ||= @options[:http_method] || :post
94 end
95
96 def debug_output
97 @debug_output ||= begin
98 case @options[:debug_output]
99 when nil, false
100 when true
101 $stdout
102 else
103 @options[:debug_output]
104 end
105 end
106 end
107
108 # The HTTP object for the site. The HTTP Object is what you get when you do Net::HTTP.new
109 def http
110 @http ||= create_http
111 end
112
113 # Contains the root URI for this site
114 def uri(custom_uri = nil)
115 if custom_uri
116 @uri = custom_uri
117 @http = create_http # yike, oh well. less intrusive this way
118 else # if no custom passed, we use existing, which, if unset, is set to site uri
119 @uri ||= URI.parse(site)
120 end
121 end
122
123 def get_access_token(request_token, request_options = {}, *arguments, &block)
124 response = token_request(http_method, (access_token_url? ? access_token_url : access_token_path), request_token, request_options, *arguments, &block)
125 OAuth::AccessToken.from_hash(self, response)
126 end
127
128 # Makes a request to the service for a new OAuth::RequestToken
129 #
130 # @request_token = @consumer.get_request_token
131 #
132 # To include OAuth parameters:
133 #
134 # @request_token = @consumer.get_request_token \
135 # :oauth_callback => "http://example.com/cb"
136 #
137 # To include application-specific parameters:
138 #
139 # @request_token = @consumer.get_request_token({}, :foo => "bar")
140 #
141 # TODO oauth_callback should be a mandatory parameter
142 def get_request_token(request_options = {}, *arguments, &block)
143 # if oauth_callback wasn't provided, it is assumed that oauth_verifiers
144 # will be exchanged out of band
145 request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND unless request_options[:exclude_callback]
146
147 if block_given?
148 response = token_request(http_method,
149 (request_token_url? ? request_token_url : request_token_path),
150 nil,
151 request_options,
152 *arguments, &block)
153 else
154 response = token_request(http_method, (request_token_url? ? request_token_url : request_token_path), nil, request_options, *arguments)
155 end
156 OAuth::RequestToken.from_hash(self, response)
157 end
158
159 # Creates, signs and performs an http request.
160 # It's recommended to use the OAuth::Token classes to set this up correctly.
161 # request_options take precedence over consumer-wide options when signing
162 # a request.
163 # arguments are POST and PUT bodies (a Hash, string-encoded parameters, or
164 # absent), followed by additional HTTP headers.
165 #
166 # @consumer.request(:get, '/people', @token, { :scheme => :query_string })
167 # @consumer.request(:post, '/people', @token, {}, @person.to_xml, { 'Content-Type' => 'application/xml' })
168 #
169 def request(http_method, path, token = nil, request_options = {}, *arguments)
170 if path !~ /^\//
171 @http = create_http(path)
172 _uri = URI.parse(path)
173 path = "#{_uri.path}#{_uri.query ? "?#{_uri.query}" : ""}"
174 end
175
176 # override the request with your own, this is useful for file uploads which Net::HTTP does not do
177 req = create_signed_request(http_method, path, token, request_options, *arguments)
178 return nil if block_given? and yield(req) == :done
179 rsp = http.request(req)
180 # check for an error reported by the Problem Reporting extension
181 # (http://wiki.oauth.net/ProblemReporting)
182 # note: a 200 may actually be an error; check for an oauth_problem key to be sure
183 if !(headers = rsp.to_hash["www-authenticate"]).nil? &&
184 (h = headers.select { |hdr| hdr =~ /^OAuth / }).any? &&
185 h.first =~ /oauth_problem/
186
187 # puts "Header: #{h.first}"
188
189 # TODO doesn't handle broken responses from api.login.yahoo.com
190 # remove debug code when done
191 params = OAuth::Helper.parse_header(h.first)
192
193 # puts "Params: #{params.inspect}"
194 # puts "Body: #{rsp.body}"
195
196 raise OAuth::Problem.new(params.delete("oauth_problem"), rsp, params)
197 end
198
199 rsp
200 end
201
202 # Creates and signs an http request.
203 # It's recommended to use the Token classes to set this up correctly
204 def create_signed_request(http_method, path, token = nil, request_options = {}, *arguments)
205 request = create_http_request(http_method, path, *arguments)
206 sign!(request, token, request_options)
207 request
208 end
209
210 # Creates a request and parses the result as url_encoded. This is used internally for the RequestToken and AccessToken requests.
211 def token_request(http_method, path, token = nil, request_options = {}, *arguments)
212 request_options[:token_request] ||= true
213 response = request(http_method, path, token, request_options, *arguments)
214 case response.code.to_i
215
216 when (200..299)
217 if block_given?
218 yield response.body
219 else
220 # symbolize keys
221 # TODO this could be considered unexpected behavior; symbols or not?
222 # TODO this also drops subsequent values from multi-valued keys
223 CGI.parse(response.body).inject({}) do |h,(k,v)|
224 h[k.strip.to_sym] = v.first
225 h[k.strip] = v.first
226 h
227 end
228 end
229 when (300..399)
230 # this is a redirect
231 uri = URI.parse(response['location'])
232 response.error! if uri.path == path # careful of those infinite redirects
233 self.token_request(http_method, uri.path, token, request_options, arguments)
234 when (400..499)
235 raise OAuth::Unauthorized, response
236 else
237 response.error!
238 end
239 end
240
241 # Sign the Request object. Use this if you have an externally generated http request object you want to sign.
242 def sign!(request, token = nil, request_options = {})
243 request.oauth!(http, self, token, options.merge(request_options))
244 end
245
246 # Return the signature_base_string
247 def signature_base_string(request, token = nil, request_options = {})
248 request.signature_base_string(http, self, token, options.merge(request_options))
249 end
250
251 def site
252 @options[:site].to_s
253 end
254
255 def request_endpoint
256 return nil if @options[:request_endpoint].nil?
257 @options[:request_endpoint].to_s
258 end
259
260 def scheme
261 @options[:scheme]
262 end
263
264 def request_token_path
265 @options[:request_token_path]
266 end
267
268 def authorize_path
269 @options[:authorize_path]
270 end
271
272 def access_token_path
273 @options[:access_token_path]
274 end
275
276 # TODO this is ugly, rewrite
277 def request_token_url
278 @options[:request_token_url] || site + request_token_path
279 end
280
281 def request_token_url?
282 @options.has_key?(:request_token_url)
283 end
284
285 def authorize_url
286 @options[:authorize_url] || site + authorize_path
287 end
288
289 def authorize_url?
290 @options.has_key?(:authorize_url)
291 end
292
293 def access_token_url
294 @options[:access_token_url] || site + access_token_path
295 end
296
297 def access_token_url?
298 @options.has_key?(:access_token_url)
299 end
300
301 def proxy
302 @options[:proxy]
303 end
304
305 protected
306
307 # Instantiates the http object
308 def create_http(_url = nil)
309
310
311 if !request_endpoint.nil?
312 _url = request_endpoint
313 end
314
315
316 if _url.nil? || _url[0] =~ /^\//
317 our_uri = URI.parse(site)
318 else
319 our_uri = URI.parse(_url)
320 end
321
322
323 if proxy.nil?
324 http_object = Net::HTTP.new(our_uri.host, our_uri.port)
325 else
326 proxy_uri = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
327 http_object = Net::HTTP.new(our_uri.host, our_uri.port, proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
328 end
329
330 http_object.use_ssl = (our_uri.scheme == 'https')
331
332 if @options[:ca_file] || CA_FILE
333 http_object.ca_file = @options[:ca_file] || CA_FILE
334 http_object.verify_mode = OpenSSL::SSL::VERIFY_PEER
335 http_object.verify_depth = 5
336 else
337 http_object.verify_mode = OpenSSL::SSL::VERIFY_NONE
338 end
339
340 http_object.read_timeout = http_object.open_timeout = @options[:timeout] || 30
341 http_object.open_timeout = @options[:open_timeout] if @options[:open_timeout]
342 http_object.ssl_version = @options[:ssl_version] if @options[:ssl_version]
343 http_object.set_debug_output(debug_output) if debug_output
344
345 http_object
346 end
347
348 # create the http request object for a given http_method and path
349 def create_http_request(http_method, path, *arguments)
350 http_method = http_method.to_sym
351
352 if [:post, :put, :patch].include?(http_method)
353 data = arguments.shift
354 end
355
356 # if the base site contains a path, add it now
357 # only add if the site host matches the current http object's host
358 # (in case we've specified a full url for token requests)
359 uri = URI.parse(site)
360 path = uri.path + path if uri.path && uri.path != '/' && uri.host == http.address
361
362 headers = arguments.first.is_a?(Hash) ? arguments.shift : {}
363
364 case http_method
365 when :post
366 request = Net::HTTP::Post.new(path,headers)
367 request["Content-Length"] = '0' # Default to 0
368 when :put
369 request = Net::HTTP::Put.new(path,headers)
370 request["Content-Length"] = '0' # Default to 0
371 when :patch
372 request = Net::HTTP::Patch.new(path,headers)
373 request["Content-Length"] = '0' # Default to 0
374 when :get
375 request = Net::HTTP::Get.new(path,headers)
376 when :delete
377 request = Net::HTTP::Delete.new(path,headers)
378 when :head
379 request = Net::HTTP::Head.new(path,headers)
380 else
381 raise ArgumentError, "Don't know how to handle http_method: :#{http_method.to_s}"
382 end
383
384 if data.is_a?(Hash)
385 request.body = OAuth::Helper.normalize(data)
386 request.content_type = 'application/x-www-form-urlencoded'
387 elsif data
388 if data.respond_to?(:read)
389 request.body_stream = data
390 if data.respond_to?(:length)
391 request["Content-Length"] = data.length.to_s
392 elsif data.respond_to?(:stat) && data.stat.respond_to?(:size)
393 request["Content-Length"] = data.stat.size.to_s
394 else
395 raise ArgumentError, "Don't know how to send a body_stream that doesn't respond to .length or .stat.size"
396 end
397 else
398 request.body = data.to_s
399 request["Content-Length"] = request.body.length.to_s
400 end
401 end
402
403 request
404 end
405
406 def marshal_dump(*args)
407 {:key => @key, :secret => @secret, :options => @options}
408 end
409
410 def marshal_load(data)
411 initialize(data[:key], data[:secret], data[:options])
412 end
413
414 end
415 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
-113
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 _escape(value.to_s.to_str)
12 rescue ArgumentError
13 _escape(value.to_s.to_str.force_encoding(Encoding::UTF_8))
14 end
15
16 def _escape(string)
17 URI::DEFAULT_PARSER.escape(string, OAuth::RESERVED_CHARACTERS)
18 end
19
20 def unescape(value)
21 URI::DEFAULT_PARSER.unescape(value.gsub('+', '%2B'))
22 end
23
24 # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word
25 # characters removed.
26 def generate_key(size=32)
27 Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
28 end
29
30 alias_method :generate_nonce, :generate_key
31
32 def generate_timestamp #:nodoc:
33 Time.now.to_i.to_s
34 end
35
36 # Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical
37 # byte value ordering. If two or more parameters share the same name, they are sorted by their value.
38 # Parameters are concatenated in their sorted order into a single string. For each parameter, the name
39 # is separated from the corresponding value by an "=" character, even if the value is empty. Each
40 # name-value pair is separated by an "&" character.
41 #
42 # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
43 def normalize(params)
44 params.sort.map do |k, values|
45 if values.is_a?(Array)
46 # make sure the array has an element so we don't lose the key
47 values << nil if values.empty?
48 # multiple values were provided for a single key
49 values.sort.collect do |v|
50 [escape(k),escape(v)] * "="
51 end
52 elsif values.is_a?(Hash)
53 normalize_nested_query(values, k)
54 else
55 [escape(k),escape(values)] * "="
56 end
57 end * "&"
58 end
59
60 #Returns a string representation of the Hash like in URL query string
61 # build_nested_query({:level_1 => {:level_2 => ['value_1','value_2']}}, 'prefix'))
62 # #=> ["prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_1", "prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_2"]
63 def normalize_nested_query(value, prefix = nil)
64 case value
65 when Array
66 value.map do |v|
67 normalize_nested_query(v, "#{prefix}[]")
68 end.flatten.sort
69 when Hash
70 value.map do |k, v|
71 normalize_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
72 end.flatten.sort
73 else
74 [escape(prefix), escape(value)] * "="
75 end
76 end
77
78 # Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and
79 # removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a
80 # valid hash. Does not validate the keys or values.
81 #
82 # hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate'])
83 # hash['oauth_timestamp']
84 # #=>"1234567890"
85 #
86 def parse_header(header)
87 # decompose
88 params = header[6,header.length].split(/[,=&]/)
89
90 # odd number of arguments - must be a malformed header.
91 raise OAuth::Problem.new("Invalid authorization header") if params.size % 2 != 0
92
93 params.map! do |v|
94 # strip and unescape
95 val = unescape(v.strip)
96 # strip quotes
97 val.sub(/^\"(.*)\"$/, '\1')
98 end
99
100 # convert into a Hash
101 Hash[*params.flatten]
102 end
103
104 def stringify_keys(hash)
105 new_h = {}
106 hash.each do |k, v|
107 new_h[k.to_s] = v.is_a?(Hash) ? stringify_keys(v) : v
108 end
109 new_h
110 end
111 end
112 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
-86
vendor/oauth/request_proxy/action_controller_request.rb less more
0 require 'active_support'
1 require "active_support/version"
2 require 'action_controller'
3 require 'uri'
4
5 if
6 Gem::Version.new(ActiveSupport::VERSION::STRING) < Gem::Version.new("3")
7 then # rails 2.x
8 require 'action_controller/request'
9 unless ActionController::Request::HTTP_METHODS.include?("patch")
10 ActionController::Request::HTTP_METHODS << "patch"
11 ActionController::Request::HTTP_METHOD_LOOKUP["PATCH"] = :patch
12 ActionController::Request::HTTP_METHOD_LOOKUP["patch"] = :patch
13 end
14
15 elsif
16 Gem::Version.new(ActiveSupport::VERSION::STRING) < Gem::Version.new("4")
17 then # rails 3.x
18 require 'action_dispatch/http/request'
19 unless ActionDispatch::Request::HTTP_METHODS.include?("patch")
20 ActionDispatch::Request::HTTP_METHODS << "patch"
21 ActionDispatch::Request::HTTP_METHOD_LOOKUP["PATCH"] = :patch
22 ActionDispatch::Request::HTTP_METHOD_LOOKUP["patch"] = :patch
23 end
24
25 else # rails 4.x and later - already has patch
26 require 'action_dispatch/http/request'
27 end
28
29 module OAuth::RequestProxy
30 class ActionControllerRequest < OAuth::RequestProxy::Base
31 proxies(defined?(ActionDispatch::AbstractRequest) ? ActionDispatch::AbstractRequest : ActionDispatch::Request)
32
33 def method
34 request.method.to_s.upcase
35 end
36
37 def uri
38 request.url
39 end
40
41 def parameters
42 if options[:clobber_request]
43 options[:parameters] || {}
44 else
45 params = request_params.merge(query_params).merge(header_params)
46 params.stringify_keys! if params.respond_to?(:stringify_keys!)
47 params.merge(options[:parameters] || {})
48 end
49 end
50
51 # Override from OAuth::RequestProxy::Base to avoid roundtrip
52 # conversion to Hash or Array and thus preserve the original
53 # parameter names
54 def parameters_for_signature
55 params = []
56 params << options[:parameters].to_query if options[:parameters]
57
58 unless options[:clobber_request]
59 params << header_params.to_query
60 params << request.query_string unless query_string_blank?
61
62 if request.post? && request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
63 params << request.raw_post
64 end
65 end
66
67 params.
68 join('&').split('&').
69 reject { |s| s.match(/\A\s*\z/) }.
70 map { |p| p.split('=').map{|esc| CGI.unescape(esc)} }.
71 reject { |kv| kv[0] == 'oauth_signature'}
72 end
73
74 protected
75
76 def query_params
77 request.query_parameters
78 end
79
80 def request_params
81 request.request_parameters
82 end
83
84 end
85 end
+0
-7
vendor/oauth/request_proxy/action_dispatch_request.rb less more
0 require 'oauth/request_proxy/rack_request'
1
2 module OAuth::RequestProxy
3 class ActionDispatchRequest < OAuth::RequestProxy::RackRequest
4 proxies ActionDispatch::Request
5 end
6 end
+0
-178
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.select { |k,v| not signature_and_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 def signature_and_unsigned_parameters
90 unsigned_parameters+["oauth_signature"]
91 end
92
93 # See 9.1.2 in specs
94 def normalized_uri
95 u = URI.parse(uri)
96 "#{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 : '/'}"
97 end
98
99 # See 9.1.1. in specs Normalize Request Parameters
100 def normalized_parameters
101 normalize(parameters_for_signature)
102 end
103
104 def sign(options = {})
105 OAuth::Signature.sign(self, options)
106 end
107
108 def sign!(options = {})
109 parameters["oauth_signature"] = sign(options)
110 @signed = true
111 signature
112 end
113
114 # See 9.1 in specs
115 def signature_base_string
116 base = [method, normalized_uri, normalized_parameters]
117 base.map { |v| escape(v) }.join("&")
118 end
119
120 # Has this request been signed yet?
121 def signed?
122 @signed
123 end
124
125 # URI, including OAuth parameters
126 def signed_uri(with_oauth = true)
127 if signed?
128 if with_oauth
129 params = parameters
130 else
131 params = non_oauth_parameters
132 end
133
134 [uri, normalize(params)] * "?"
135 else
136 STDERR.puts "This request has not yet been signed!"
137 end
138 end
139
140 # Authorization header for OAuth
141 def oauth_header(options = {})
142 header_params_str = oauth_parameters.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
143
144 realm = "realm=\"#{options[:realm]}\", " if options[:realm]
145 "OAuth #{realm}#{header_params_str}"
146 end
147
148 def query_string_blank?
149 if uri = request.env['REQUEST_URI']
150 uri.split('?', 2)[1].nil?
151 else
152 request.query_string.match(/\A\s*\z/)
153 end
154 end
155
156 protected
157
158 def header_params
159 %w( X-HTTP_AUTHORIZATION Authorization HTTP_AUTHORIZATION ).each do |header|
160 next unless request.env.include?(header)
161
162 header = request.env[header]
163 next unless header[0,6] == 'OAuth '
164
165 # parse the header into a Hash
166 oauth_params = OAuth::Helper.parse_header(header)
167
168 # remove non-OAuth parameters
169 oauth_params.reject! { |k,v| k !~ /^oauth_/ }
170
171 return oauth_params
172 end
173
174 return {}
175 end
176 end
177 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 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
-62
vendor/oauth/request_proxy/rest_client_request.rb less more
0 require 'oauth/request_proxy/base'
1 require 'rest-client'
2 require 'uri'
3 require 'cgi'
4
5 module OAuth::RequestProxy::RestClient
6 class Request < OAuth::RequestProxy::Base
7 proxies RestClient::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 post_parameters.merge(query_params).merge(options[:parameters] || {})
22 end
23 end
24
25 protected
26
27 def query_params
28 query = URI.parse(request.url).query
29 query ? CGI.parse(query) : {}
30 end
31
32 def request_params
33 end
34
35 def post_parameters
36 # Post params are only used if posting form data
37 if method == 'POST' || method == 'PUT'
38 OAuth::Helper.stringify_keys(query_string_to_hash(request.payload.to_s) || {})
39 else
40 {}
41 end
42 end
43
44 private
45
46 def query_string_to_hash(query)
47 keyvals = query.split('&').inject({}) do |result, q|
48 k,v = q.split('=')
49 if !v.nil?
50 result.merge({k => v})
51 elsif !result.key?(k)
52 result.merge({k => true})
53 else
54 result
55 end
56 end
57 keyvals
58 end
59
60 end
61 end
+0
-54
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.options[: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 = request.options[:method].to_s.upcase
22 request_method.empty? ? 'GET' : request_method
23 end
24
25 def uri
26 options[:uri].to_s
27 end
28
29 def parameters
30 if options[:clobber_request]
31 options[:parameters]
32 else
33 post_parameters.merge(query_parameters).merge(options[:parameters] || {})
34 end
35 end
36
37 private
38
39 def query_parameters
40 query = URI.parse(request.url).query
41 query ? CGI.parse(query) : {}
42 end
43
44 def post_parameters
45 # Post params are only used if posting form data
46 if method == 'POST'
47 OAuth::Helper.stringify_keys(request.options[:params] || {})
48 else
49 {}
50 end
51 end
52 end
53 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
-96
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 initialize(request, options = {}, &block)
19 raise TypeError unless request.kind_of?(OAuth::RequestProxy::Base)
20 @request = request
21 @options = options
22
23 ## consumer secret was determined beforehand
24
25 @consumer_secret = options[:consumer].secret if options[:consumer]
26
27 # presence of :consumer_secret option will override any Consumer that's provided
28 @consumer_secret = options[:consumer_secret] if options[:consumer_secret]
29
30 ## token secret was determined beforehand
31
32 @token_secret = options[:token].secret if options[:token]
33
34 # presence of :token_secret option will override any Token that's provided
35 @token_secret = options[:token_secret] if options[:token_secret]
36
37 # override secrets based on the values returned from the block (if any)
38 if block_given?
39 # consumer secret and token secret need to be looked up based on pieces of the request
40 secrets = yield block.arity == 1 ? request : [token, consumer_key, nonce, request.timestamp]
41 if secrets.is_a?(Array) && secrets.size == 2
42 @token_secret = secrets[0]
43 @consumer_secret = secrets[1]
44 end
45 end
46 end
47
48 def signature
49 Base64.encode64(digest).chomp.gsub(/\n/,'')
50 end
51
52 def ==(cmp_signature)
53 signature == cmp_signature
54 end
55
56 def verify
57 self == self.request.signature
58 end
59
60 def signature_base_string
61 request.signature_base_string
62 end
63
64 def body_hash
65 raise_instantiation_error
66 end
67
68 private
69
70 def token
71 request.token
72 end
73
74 def consumer_key
75 request.consumer_key
76 end
77
78 def nonce
79 request.nonce
80 end
81
82 def secret
83 "#{escape(consumer_secret)}&#{escape(token_secret)}"
84 end
85
86 def digest
87 raise_instantiation_error
88 end
89
90 def raise_instantiation_error
91 raise NotImplementedError, "Cannot instantiate #{self.class.name} class directly."
92 end
93
94 end
95 end
+0
-17
vendor/oauth/signature/hmac/sha1.rb less more
0 require 'oauth/signature/base'
1
2 module OAuth::Signature::HMAC
3 class SHA1 < OAuth::Signature::Base
4 implements 'hmac-sha1'
5
6 def body_hash
7 Base64.encode64(OpenSSL::Digest::SHA1.digest(request.body || '')).chomp.gsub(/\n/,'')
8 end
9
10 private
11
12 def digest
13 OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), secret, signature_base_string)
14 end
15 end
16 end
+0
-29
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 body_hash
19 nil
20 end
21
22 private
23
24 def secret
25 super
26 end
27 end
28 end
+0
-50
vendor/oauth/signature/rsa/sha1.rb less more
0 require 'oauth/signature/base'
1
2 module OAuth::Signature::RSA
3 class SHA1 < OAuth::Signature::Base
4 implements 'rsa-sha1'
5
6 def ==(cmp_signature)
7 public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(cmp_signature.is_a?(Array) ? cmp_signature.first : cmp_signature), signature_base_string)
8 end
9
10 def public_key
11 if consumer_secret.is_a?(String)
12 decode_public_key
13 elsif consumer_secret.is_a?(OpenSSL::X509::Certificate)
14 consumer_secret.public_key
15 else
16 consumer_secret
17 end
18 end
19
20 def body_hash
21 Base64.encode64(OpenSSL::Digest::SHA1.digest(request.body || '')).chomp.gsub(/\n/,'')
22 end
23
24 private
25
26 def decode_public_key
27 case consumer_secret
28 when /-----BEGIN CERTIFICATE-----/
29 OpenSSL::X509::Certificate.new( consumer_secret).public_key
30 else
31 OpenSSL::PKey::RSA.new( consumer_secret)
32 end
33 end
34
35 def digest
36 private_key = OpenSSL::PKey::RSA.new(
37 if options[:private_key_file]
38 IO.read(options[:private_key_file])
39 elsif options[:private_key]
40 options[:private_key]
41 else
42 consumer_secret
43 end
44 )
45
46 private_key.sign(OpenSSL::Digest::SHA1.new, signature_base_string)
47 end
48 end
49 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
-83
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 PATCH request using AccessToken
62 #
63 # @response = @token.patch('/people/123')
64 # @response = @token.patch('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' })
65 # @response = @token.patch('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' })
66 # @response = @token.patch('/people/123', nil, { 'Accept' => 'application/xml' })
67 # @response = @token.patch('/people/123', @person.to_xml, { 'Accept' => 'application/xml', 'Content-Type' => 'application/xml' })
68 #
69 def patch(path, body = '', headers = {})
70 request(:patch, path, body, headers)
71 end
72
73 # Make a regular DELETE request using AccessToken
74 #
75 # @response = @token.delete('/people/123')
76 # @response = @token.delete('/people/123', { 'Accept' => 'application/xml' })
77 #
78 def delete(path, headers = {})
79 request(:delete, path, headers)
80 end
81 end
82 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
-37
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 return nil if self.token.nil?
8
9 params = (params || {}).merge(:oauth_token => self.token)
10 build_authorize_url(consumer.authorize_url, params)
11 end
12
13 def callback_confirmed?
14 params[:oauth_callback_confirmed] == "true"
15 end
16
17 # exchange for AccessToken on server
18 def get_access_token(options = {}, *arguments)
19 response = consumer.token_request(consumer.http_method, (consumer.access_token_url? ? consumer.access_token_url : consumer.access_token_path), self, options, *arguments)
20 OAuth::AccessToken.from_hash(consumer, response)
21 end
22
23 protected
24
25 # construct an authorization url
26 def build_authorize_url(base_url, params)
27 uri = URI.parse(base_url.to_s)
28 queries = {}
29 queries = Hash[URI.decode_www_form(uri.query)] if uri.query
30 # TODO doesn't handle array values correctly
31 queries.merge!(params) if params
32 uri.query = URI.encode_www_form(queries) if !queries.empty?
33 uri.to_s
34 end
35 end
36 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_token_secret=#{escape(secret)}"
14 end
15 end
16 end
+0
-3
vendor/oauth/version.rb less more
0 module OAuth
1 VERSION = "0.5.4"
2 end
+0
-11
vendor/oauth.rb less more
0 root = File.dirname(__FILE__)
1 $LOAD_PATH << root unless $LOAD_PATH.include?(root)
2
3 require 'oauth/version'
4
5 require 'oauth/oauth'
6
7 require 'oauth/client/helper'
8 require 'oauth/signature/hmac/sha1'
9 require 'oauth/signature/rsa/sha1'
10 require 'oauth/request_proxy/mock_request'
+0
-14
vendor/pluggaloid/error.rb less more
0 # -*- coding: utf-8 -*-
1 module Pluggaloid
2 class Error < ::StandardError; end
3
4 class ArgumentError < Error; end
5
6 class TypeError < Error; end
7
8 class FilterError < Error; end
9
10 class NoDefaultDelayerError < Error; end
11
12 class DuplicateListenerSlugError < Error; end
13 end
+0
-132
vendor/pluggaloid/event.rb less more
0 # -*- coding: utf-8 -*-
1
2 class Pluggaloid::Event
3 Lock = Mutex.new
4
5 include InstanceStorage
6
7 # オプション。以下のキーを持つHash
8 # :prototype :: 引数の数と型。Arrayで、type_strictが解釈できる条件を設定する
9 # :priority :: Delayerの優先順位
10 attr_accessor :options
11
12 # フィルタを別のスレッドで実行する。偽ならメインスレッドでフィルタを実行する
13 @filter_another_thread = false
14
15 def initialize(*args)
16 super
17 @options = {}
18 @listeners = [].freeze
19 @filters = [].freeze
20 end
21
22 def vm
23 self.class.vm end
24
25 # イベントの優先順位を取得する
26 # ==== Return
27 # プラグインの優先順位
28 def priority
29 if @options.has_key? :priority
30 @options[:priority] end end
31
32 # イベントを引数 _args_ で発生させる
33 # ==== Args
34 # [*args] イベントの引数
35 # ==== Return
36 # Delayerか、イベントを待ち受けているリスナがない場合はnil
37 def call(*args)
38 if self.class.filter_another_thread
39 if @filters.empty?
40 vm.Delayer.new(*Array(priority)) do
41 call_all_listeners(args) end
42 else
43 Thread.new do
44 filtered_args = filtering(*args)
45 if filtered_args.is_a? Array
46 vm.Delayer.new(*Array(priority)) do
47 call_all_listeners(filtered_args) end end end end
48 else
49 vm.Delayer.new(*Array(priority)) do
50 args = filtering(*args) if not @filters.empty?
51 call_all_listeners(args) if args.is_a? Array end end end
52
53 # 引数 _args_ をフィルタリングした結果を返す
54 # ==== Args
55 # [*args] 引数
56 # ==== Return
57 # フィルタされた引数の配列
58 def filtering(*args)
59 catch(:filter_exit) {
60 @filters.reduce(args){ |acm, event_filter|
61 event_filter.filtering(*acm) } } end
62
63 def add_listener(listener)
64 unless listener.is_a? Pluggaloid::Listener
65 raise Pluggaloid::ArgumentError, "First argument must be Pluggaloid::Listener, but given #{listener.class}."
66 end
67 Lock.synchronize do
68 if @listeners.map(&:slug).include?(listener.slug)
69 raise Pluggaloid::DuplicateListenerSlugError, "Listener slug #{listener.slug} already exists."
70 end
71 @listeners = [*@listeners, listener].freeze
72 end
73 self
74 end
75
76 def delete_listener(listener)
77 Lock.synchronize do
78 @listeners = @listeners.dup
79 @listeners.delete(listener)
80 @listeners.freeze
81 end
82 self
83 end
84
85 # イベントフィルタを追加する
86 # ==== Args
87 # [event_filter] イベントフィルタ(Filter)
88 # ==== Return
89 # self
90 def add_filter(event_filter)
91 unless event_filter.is_a? Pluggaloid::Filter
92 raise Pluggaloid::ArgumentError, "First argument must be Pluggaloid::Filter, but given #{event_filter.class}." end
93 @filters = [*@filters, event_filter].freeze
94 self
95 end
96
97 # イベントフィルタを削除する
98 # ==== Args
99 # [event_filter] イベントフィルタ(EventFilter)
100 # ==== Return
101 # self
102 def delete_filter(event_filter)
103 Lock.synchronize do
104 @filters = @filters.dup
105 @filters.delete(event_filter)
106 @filters.freeze
107 end
108 self
109 end
110
111 private
112 def call_all_listeners(args)
113 catch(:plugin_exit) do
114 @listeners.each do |listener|
115 listener.call(*args)
116 end
117 end
118 end
119
120 class << self
121 attr_accessor :filter_another_thread, :vm
122
123 alias __clear_aF4e__ clear!
124 def clear!
125 @filter_another_thread = false
126 __clear_aF4e__()
127 end
128 end
129
130 clear!
131 end
+0
-51
vendor/pluggaloid/filter.rb less more
0 # -*- coding: utf-8 -*-
1
2 class Pluggaloid::Filter < Pluggaloid::Handler
3 NotConverted = Class.new
4 THROUGH = NotConverted.new.freeze
5
6 # フィルタ内部で使う。フィルタの実行をキャンセルする。Plugin#filtering はfalseを返し、
7 # イベントのフィルタの場合は、そのイベントの実行自体をキャンセルする。
8 # また、 _result_ が渡された場合、Event#filtering の戻り値は _result_ になる。
9 def self.cancel!(result=false)
10 throw :filter_exit, result end
11
12 CANCEL_PROC = method(:cancel!)
13
14 # ==== Args
15 # [event] 監視するEventのインスタンス
16 # [name:] 名前(String | nil)
17 # [slug:] フィルタスラッグ(Symbol | nil)
18 # [tags:] Pluggaloid::HandlerTag|Array フィルタのタグ
19 # [&callback] コールバック
20 def initialize(event, **kwrest, &callback)
21 super
22 @callback = callback
23 event.add_filter self end
24
25 # イベントを実行する
26 # ==== Args
27 # [*args] イベントの引数
28 # ==== Return
29 # 加工後の引数の配列
30 def filtering(*args)
31 length = args.size
32 result = @callback.call(*args, &CANCEL_PROC)
33 case
34 when THROUGH == result
35 args
36 when length != result.size
37 raise Pluggaloid::FilterError, "filter changes arguments length (#{length} to #{result.size})"
38 else
39 result
40 end
41 end
42
43 # このリスナを削除する
44 # ==== Return
45 # self
46 def detach
47 @event.delete_filter(self)
48 self end
49
50 end
+0
-45
vendor/pluggaloid/handler.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin rdoc
3 イベントのListenerやFilterのスーパクラス。
4 イベントに関連付けたり、タグを付けたりできる
5 =end
6 class Pluggaloid::Handler < Pluggaloid::Identity
7 Lock = Mutex.new
8 attr_reader :tags
9
10 # ==== Args
11 # [event] 監視するEventのインスタンス
12 # [name:] 名前(String | nil)
13 # [slug:] ハンドラスラッグ(Symbol | nil)
14 # [tags:] Pluggaloid::HandlerTag|Array リスナのタグ
15 # [&callback] コールバック
16 def initialize(event, tags: [], **kwrest)
17 raise Pluggaloid::TypeError, "Argument `event' must be instance of Pluggaloid::Event, but given #{event.class}." unless event.is_a? Pluggaloid::Event
18 super(**kwrest)
19 @event = event
20 _tags = tags.is_a?(Pluggaloid::HandlerTag) ? [tags] : Array(tags)
21 _tags.each{|t| raise "#{t} is not a Pluggaloid::HandlerTag" unless t.is_a?(Pluggaloid::HandlerTag) }
22 @tags = Set.new(_tags).freeze
23 end
24
25 def add_tag(tag)
26 raise Pluggaloid::TypeError, "Argument `tag' must be instance of Pluggaloid::HandlerTag, but given #{tag.class}." unless tag.is_a? Pluggaloid::HandlerTag
27 Lock.synchronize do
28 @tags = Set.new([tag, *@tags]).freeze
29 end
30 self
31 end
32
33 def remove_tag(tag)
34 Lock.synchronize do
35 @tags -= tag
36 @tags.freeze
37 end
38 self
39 end
40
41 def inspect
42 "#<#{self.class} event: #{@event.name.inspect}, slug: #{slug.inspect}, name: #{name.inspect}>"
43 end
44 end
+0
-86
vendor/pluggaloid/handler_tag.rb less more
0 # -*- coding: utf-8 -*-
1
2 require 'securerandom'
3
4 =begin rdoc
5 = リスナをまとめて管理するプラグイン
6
7 Pluggaloid::Listener や、 Pluggaloid::Filter をまとめて扱うための仕組み。
8 Pluggaloid::Plugin#add_event などの引数 _tags:_ に、このインスタンスを設定する。
9
10 == インスタンスの作成
11
12 Pluggaloid::Plugin#handler_tag を使って生成する。 Pluggaloid::HandlerTag の
13 _plugin:_ 引数には、レシーバ(Pluggaloid::Plugin)が渡される。
14 Pluggaloid::HandlerTag は、このプラグインの中でだけ使える。複数のプラグインのリスナ
15 をまとめて管理することはできない。
16
17 == リスナにタグをつける
18
19 Pluggaloid::Plugin#add_event または Pluggaloid::Plugin#add_event_filter の
20 _tags:_ 引数にこれのインスタンスを渡す。
21
22 == このタグがついたListenerやFilterを取得する
23
24 Enumerable をincludeしていて、リスナやフィルタを取得することができる。
25 また、
26 - Pluggaloid::HandlerTag#listeners で、 Pluggaloid::Listener だけ
27 - Pluggaloid::HandlerTag#filters で、 Pluggaloid::Filter だけ
28 を対象にした Enumerator を取得することができる
29
30 == このタグがついたリスナを全てdetachする
31
32 Pluggaloid::Plugin#detach の第一引数に Pluggaloid::HandlerTag の
33 インスタンスを渡すことで、そのHandlerTagがついたListener、Filterは全てデタッチ
34 される
35
36 =end
37 class Pluggaloid::HandlerTag < Pluggaloid::Identity
38 include Enumerable
39
40 # ==== Args
41 # [name:] タグの名前(String | nil)
42 def initialize(plugin:, **kwrest)
43 super(**kwrest)
44 @plugin = plugin
45 end
46
47 # このTagがついている Pluggaloid::Listener と Pluggaloid::Filter を全て列挙する
48 # ==== Return
49 # Enumerable
50 def each(&block)
51 if block
52 Enumerator.new do |y|
53 listeners{|x| y << x }
54 filters{|x| y << x }
55 end.each(&block)
56 else
57 Enumerator.new do |y|
58 listeners{|x| y << x }
59 filters{|x| y << x }
60 end
61 end
62 end
63
64 # このTagがついている Pluggaloid::Listener を全て列挙する
65 # ==== Return
66 # Enumerable
67 def listeners(&block)
68 if block
69 listeners.each(&block)
70 else
71 @plugin.to_enum(:listeners).lazy.select{|l| l.tags.include?(self) }
72 end
73 end
74
75 # このTagがついている Pluggaloid::Filter を全て列挙する
76 # ==== Return
77 # Enumerable
78 def filters(&block)
79 if block
80 filters.each(&block)
81 else
82 @plugin.to_enum(:filters).lazy.select{|l| l.tags.include?(self) }
83 end
84 end
85 end
+0
-21
vendor/pluggaloid/identity.rb less more
0 # -*- coding: utf-8 -*-
1
2 =begin rdoc
3 slugと名前をもつオブジェクト。
4 これの参照を直接持たずとも、slugで一意に参照したり、表示名を設定することができる
5 =end
6 class Pluggaloid::Identity
7 attr_reader :name, :slug
8
9 # ==== Args
10 # [name:] 名前(String | nil)
11 # [slug:] ハンドラスラッグ(Symbol | nil)
12 def initialize(slug: SecureRandom.uuid, name: slug)
13 @name = name.to_s.freeze
14 @slug = slug.to_sym
15 end
16
17 def inspect
18 "#<#{self.class} slug: #{slug.inspect}, name: #{name.inspect}>"
19 end
20 end
+0
-33
vendor/pluggaloid/listener.rb less more
0 # -*- coding: utf-8 -*-
1 require 'securerandom'
2 require 'set'
3
4 class Pluggaloid::Listener < Pluggaloid::Handler
5 # プラグインコールバックをこれ以上実行しない。
6 def self.cancel!
7 throw :plugin_exit, false end
8
9 # ==== Args
10 # [event] 監視するEventのインスタンス
11 # [name:] 名前(String | nil)
12 # [slug:] イベントリスナスラッグ(Symbol | nil)
13 # [tags:] Pluggaloid::HandlerTag|Array リスナのタグ
14 # [&callback] コールバック
15 def initialize(event, **kwrest, &callback)
16 super
17 @callback = callback
18 event.add_listener(self) end
19
20 # イベントを実行する
21 # ==== Args
22 # [*args] イベントの引数
23 def call(*args)
24 @callback.call(*args, &self.class.method(:cancel!)) end
25
26 # このリスナを削除する
27 # ==== Return
28 # self
29 def detach
30 @event.delete_listener(self)
31 self end
32 end
+0
-265
vendor/pluggaloid/plugin.rb less more
0 # -*- coding: utf-8 -*-
1
2 require 'instance_storage'
3 require 'delayer'
4 require 'securerandom'
5 require 'set'
6
7 # プラグインの本体。
8 # DSLを提供し、イベントやフィルタの管理をする
9 module Pluggaloid
10 class Plugin
11 include InstanceStorage
12
13 class << self
14 attr_writer :vm
15
16 def vm
17 @vm ||= begin
18 raise Pluggaloid::NoDefaultDelayerError, "Default Delayer was not set." unless Delayer.default
19 vm = Pluggaloid::VM.new(
20 Delayer.default,
21 self,
22 Pluggaloid::Event,
23 Pluggaloid::Listener,
24 Pluggaloid::Filter,
25 Pluggaloid::HandlerTag)
26 vm.Event.vm = vm end end
27
28 # プラグインのインスタンスを返す。
29 # ブロックが渡された場合、そのブロックをプラグインのインスタンスのスコープで実行する
30 # ==== Args
31 # [plugin_name] プラグイン名
32 # ==== Return
33 # Plugin
34 def create(plugin_name, &body)
35 self[plugin_name].instance_eval(&body) if body
36 self[plugin_name] end
37
38 # イベントを宣言する。
39 # ==== Args
40 # [event_name] イベント名
41 # [options] 以下のキーを持つHash
42 # :prototype :: 引数の数と型。Arrayで、type_strictが解釈できる条件を設定する
43 # :priority :: Delayerの優先順位
44 def defevent(event_name, options = {})
45 vm.Event[event_name].options = options end
46
47 # イベント _event_name_ を発生させる
48 # ==== Args
49 # [event_name] イベント名
50 # [*args] イベントの引数
51 # ==== Return
52 # Delayer
53 def call(event_name, *args)
54 vm.Event[event_name].call(*args) end
55
56 # 引数 _args_ をフィルタリングした結果を返す
57 # ==== Args
58 # [*args] 引数
59 # ==== Return
60 # フィルタされた引数の配列
61 def filtering(event_name, *args)
62 vm.Event[event_name].filtering(*args) end
63
64 # 互換性のため
65 def uninstall(plugin_name)
66 self[plugin_name].uninstall end
67
68 # 互換性のため
69 def filter_cancel!
70 vm.Filter.cancel! end
71
72 alias plugin_list instances_name
73
74 alias __clear_aF4e__ clear!
75 def clear!
76 if defined?(@vm) and @vm
77 @vm.Event.clear!
78 @vm = nil end
79 __clear_aF4e__() end
80 end
81
82 # プラグインの名前
83 attr_reader :name
84
85 # spec
86 attr_accessor :spec
87
88 # 最初にプラグインがロードされた時刻(uninstallされるとリセットする)
89 attr_reader :defined_time
90
91 # ==== Args
92 # [plugin_name] プラグイン名
93 def initialize(*args)
94 super
95 @defined_time = Time.new.freeze
96 @events = Set.new
97 @filters = Set.new
98 end
99
100 # イベントリスナを新しく登録する
101 # ==== Args
102 # [event] 監視するEventのインスタンス
103 # [name:] 名前(String | nil)
104 # [slug:] イベントリスナスラッグ(Symbol | nil)
105 # [tags:] Pluggaloid::HandlerTag|Array リスナのタグ
106 # [&callback] コールバック
107 # ==== Return
108 # Pluggaloid::Listener
109 def add_event(event_name, **kwrest, &callback)
110 result = vm.Listener.new(vm.Event[event_name], **kwrest, &callback)
111 @events << result
112 result end
113
114 # イベントフィルタを新しく登録する
115 # ==== Args
116 # [event] 監視するEventのインスタンス
117 # [name:] 名前(String | nil)
118 # [slug:] フィルタスラッグ(Symbol | nil)
119 # [tags:] Pluggaloid::HandlerTag|Array フィルタのタグ
120 # [&callback] コールバック
121 # ==== Return
122 # Pluggaloid::Filter
123 def add_event_filter(event_name, **kwrest, &callback)
124 result = vm.Filter.new(vm.Event[event_name], **kwrest, &callback)
125 @filters << result
126 result end
127
128 # このプラグインのHandlerTagを作る。
129 # ブロックが渡された場合は、ブロックの中を実行し、ブロックの中で定義された
130 # Handler全てにTagを付与する。
131 # ==== Args
132 # [slug] スラッグ
133 # [name] タグ名
134 # ==== Return
135 # Pluggaloid::HandlerTag
136 def handler_tag(slug=SecureRandom.uuid, name=slug, &block)
137 tag = case slug
138 when String, Symbol
139 vm.HandlerTag.new(slug: slug.to_sym, name: name.to_s, plugin: self)
140 when vm.HandlerTag
141 slug
142 else
143 raise Pluggaloid::TypeError, "Argument `slug' must be instance of Symbol, String or Pluggaloid::HandlerTag, but given #{slug.class}."
144 end
145 if block
146 handlers = @events + @filters
147 block.(tag)
148 (@events + @filters - handlers).each do |handler|
149 handler.add_tag(tag)
150 end
151 else
152 tag
153 end
154 end
155
156 # イベントリスナを列挙する
157 # ==== Return
158 # Set of Pluggaloid::Listener
159 def listeners(&block)
160 if block
161 @events.each(&block)
162 else
163 @events.dup
164 end
165 end
166
167 # フィルタを列挙する
168 # ==== Return
169 # Set of Pluggaloid::Filter
170 def filters(&block)
171 if block
172 @filters.each(&block)
173 else
174 @filters.dup
175 end
176 end
177
178
179 # イベントを削除する。
180 # 引数は、Pluggaloid::ListenerかPluggaloid::Filterのみ(on_*やfilter_*の戻り値)。
181 # 互換性のため、二つ引数がある場合は第一引数は無視され、第二引数が使われる。
182 # ==== Args
183 # [*args] 引数
184 # ==== Return
185 # self
186 def detach(*args)
187 listener = args.last
188 case listener
189 when vm.Listener
190 @events.delete(listener)
191 listener.detach
192 when vm.Filter
193 @filters.delete(listener)
194 listener.detach
195 when Enumerable
196 listener.each(&method(:detach))
197 else
198 raise ArgumentError, "Argument must be Pluggaloid::Listener, Pluggaloid::Filter, Pluggaloid::HandlerTag or Enumerable. But given #{listener.class}."
199 end
200 self end
201
202 # このプラグインを破棄する
203 # ==== Return
204 # self
205 def uninstall
206 vm.Event[:unload].call(self.name)
207 vm.Delayer.new do
208 @events.map(&:detach)
209 @filters.map(&:detach)
210 self.class.destroy name
211 end
212 self end
213
214 # イベント _event_name_ を宣言する
215 # ==== Args
216 # [event_name] イベント名
217 # [options] イベントの定義
218 def defevent(event_name, options={})
219 vm.Event[event_name].options.merge!({plugin: self}.merge(options)) end
220
221 # DSLメソッドを新しく追加する。
222 # 追加されたメソッドは呼ぶと &callback が呼ばれ、その戻り値が返される。引数も順番通り全て &callbackに渡される
223 # ==== Args
224 # [dsl_name] 新しく追加するメソッド名
225 # [&callback] 実行されるメソッド
226 # ==== Return
227 # self
228 def defdsl(dsl_name, &callback)
229 self.class.instance_eval {
230 define_method(dsl_name, &callback) }
231 self end
232
233 # プラグインが Plugin.uninstall される時に呼ばれるブロックを登録する。
234 def onunload(&callback)
235 add_event(:unload) do |plugin_slug|
236 if plugin_slug == self.name
237 callback.call
238 end
239 end
240 end
241 alias :on_unload :onunload
242
243 # マジックメソッドを追加する。
244 # on_?name :: add_event(name)
245 # filter_?name :: add_event_filter(name)
246 def method_missing(method, *args, **kwrest, &proc)
247 method_name = method.to_s
248 case
249 when method_name.start_with?('on')
250 event_name = method_name[(method_name[2] == '_' ? 3 : 2)..method_name.size]
251 add_event(event_name.to_sym, *args, **kwrest, &proc)
252 when method_name.start_with?('filter')
253 event_name = method_name[(method_name[6] == '_' ? 7 : 6)..method_name.size]
254 add_event_filter(event_name.to_sym, **kwrest, &proc)
255 end
256 end
257
258 private
259
260 def vm
261 self.class.vm end
262
263 end
264 end
+0
-3
vendor/pluggaloid/version.rb less more
0 module Pluggaloid
1 VERSION = "1.2.0"
2 end
+0
-25
vendor/pluggaloid.rb less more
0 require "pluggaloid/version"
1 require "pluggaloid/plugin"
2 require 'pluggaloid/event'
3 require "pluggaloid/identity"
4 require "pluggaloid/handler"
5 require 'pluggaloid/listener'
6 require 'pluggaloid/filter'
7 require "pluggaloid/handler_tag"
8 require 'pluggaloid/error'
9
10 require 'delayer'
11
12 module Pluggaloid
13 VM = Struct.new(*%i<Delayer Plugin Event Listener Filter HandlerTag>)
14
15 def self.new(delayer)
16 vm = VM.new(delayer,
17 Class.new(Plugin),
18 Class.new(Event),
19 Class.new(Listener),
20 Class.new(Filter),
21 Class.new(HandlerTag))
22 vm.Plugin.vm = vm.Event.vm = vm
23 end
24 end
+0
-235
vendor/public_suffix/domain.rb less more
0 # frozen_string_literal: true
1
2 # = Public Suffix
3 #
4 # Domain name parser based on the Public Suffix List.
5 #
6 # Copyright (c) 2009-2019 Simone Carletti <weppos@weppos.net>
7
8 module PublicSuffix
9
10 # Domain represents a domain name, composed by a TLD, SLD and TRD.
11 class Domain
12
13 # Splits a string into the labels, that is the dot-separated parts.
14 #
15 # The input is not validated, but it is assumed to be a valid domain name.
16 #
17 # @example
18 #
19 # name_to_labels('example.com')
20 # # => ['example', 'com']
21 #
22 # name_to_labels('example.co.uk')
23 # # => ['example', 'co', 'uk']
24 #
25 # @param name [String, #to_s] The domain name to split.
26 # @return [Array<String>]
27 def self.name_to_labels(name)
28 name.to_s.split(DOT)
29 end
30
31
32 attr_reader :tld, :sld, :trd
33
34 # Creates and returns a new {PublicSuffix::Domain} instance.
35 #
36 # @overload initialize(tld)
37 # Initializes with a +tld+.
38 # @param [String] tld The TLD (extension)
39 # @overload initialize(tld, sld)
40 # Initializes with a +tld+ and +sld+.
41 # @param [String] tld The TLD (extension)
42 # @param [String] sld The TRD (domain)
43 # @overload initialize(tld, sld, trd)
44 # Initializes with a +tld+, +sld+ and +trd+.
45 # @param [String] tld The TLD (extension)
46 # @param [String] sld The SLD (domain)
47 # @param [String] trd The TRD (subdomain)
48 #
49 # @yield [self] Yields on self.
50 # @yieldparam [PublicSuffix::Domain] self The newly creates instance
51 #
52 # @example Initialize with a TLD
53 # PublicSuffix::Domain.new("com")
54 # # => #<PublicSuffix::Domain @tld="com">
55 #
56 # @example Initialize with a TLD and SLD
57 # PublicSuffix::Domain.new("com", "example")
58 # # => #<PublicSuffix::Domain @tld="com", @trd=nil>
59 #
60 # @example Initialize with a TLD, SLD and TRD
61 # PublicSuffix::Domain.new("com", "example", "wwww")
62 # # => #<PublicSuffix::Domain @tld="com", @trd=nil, @sld="example">
63 #
64 def initialize(*args)
65 @tld, @sld, @trd = args
66 yield(self) if block_given?
67 end
68
69 # Returns a string representation of this object.
70 #
71 # @return [String]
72 def to_s
73 name
74 end
75
76 # Returns an array containing the domain parts.
77 #
78 # @return [Array<String, nil>]
79 #
80 # @example
81 #
82 # PublicSuffix::Domain.new("google.com").to_a
83 # # => [nil, "google", "com"]
84 #
85 # PublicSuffix::Domain.new("www.google.com").to_a
86 # # => [nil, "google", "com"]
87 #
88 def to_a
89 [@trd, @sld, @tld]
90 end
91
92 # Returns the full domain name.
93 #
94 # @return [String]
95 #
96 # @example Gets the domain name of a domain
97 # PublicSuffix::Domain.new("com", "google").name
98 # # => "google.com"
99 #
100 # @example Gets the domain name of a subdomain
101 # PublicSuffix::Domain.new("com", "google", "www").name
102 # # => "www.google.com"
103 #
104 def name
105 [@trd, @sld, @tld].compact.join(DOT)
106 end
107
108 # Returns a domain-like representation of this object
109 # if the object is a {#domain?}, <tt>nil</tt> otherwise.
110 #
111 # PublicSuffix::Domain.new("com").domain
112 # # => nil
113 #
114 # PublicSuffix::Domain.new("com", "google").domain
115 # # => "google.com"
116 #
117 # PublicSuffix::Domain.new("com", "google", "www").domain
118 # # => "www.google.com"
119 #
120 # This method doesn't validate the input. It handles the domain
121 # as a valid domain name and simply applies the necessary transformations.
122 #
123 # This method returns a FQD, not just the domain part.
124 # To get the domain part, use <tt>#sld</tt> (aka second level domain).
125 #
126 # PublicSuffix::Domain.new("com", "google", "www").domain
127 # # => "google.com"
128 #
129 # PublicSuffix::Domain.new("com", "google", "www").sld
130 # # => "google"
131 #
132 # @see #domain?
133 # @see #subdomain
134 #
135 # @return [String]
136 def domain
137 [@sld, @tld].join(DOT) if domain?
138 end
139
140 # Returns a subdomain-like representation of this object
141 # if the object is a {#subdomain?}, <tt>nil</tt> otherwise.
142 #
143 # PublicSuffix::Domain.new("com").subdomain
144 # # => nil
145 #
146 # PublicSuffix::Domain.new("com", "google").subdomain
147 # # => nil
148 #
149 # PublicSuffix::Domain.new("com", "google", "www").subdomain
150 # # => "www.google.com"
151 #
152 # This method doesn't validate the input. It handles the domain
153 # as a valid domain name and simply applies the necessary transformations.
154 #
155 # This method returns a FQD, not just the subdomain part.
156 # To get the subdomain part, use <tt>#trd</tt> (aka third level domain).
157 #
158 # PublicSuffix::Domain.new("com", "google", "www").subdomain
159 # # => "www.google.com"
160 #
161 # PublicSuffix::Domain.new("com", "google", "www").trd
162 # # => "www"
163 #
164 # @see #subdomain?
165 # @see #domain
166 #
167 # @return [String]
168 def subdomain
169 [@trd, @sld, @tld].join(DOT) if subdomain?
170 end
171
172 # Checks whether <tt>self</tt> looks like a domain.
173 #
174 # This method doesn't actually validate the domain.
175 # It only checks whether the instance contains
176 # a value for the {#tld} and {#sld} attributes.
177 #
178 # @example
179 #
180 # PublicSuffix::Domain.new("com").domain?
181 # # => false
182 #
183 # PublicSuffix::Domain.new("com", "google").domain?
184 # # => true
185 #
186 # PublicSuffix::Domain.new("com", "google", "www").domain?
187 # # => true
188 #
189 # # This is an invalid domain, but returns true
190 # # because this method doesn't validate the content.
191 # PublicSuffix::Domain.new("com", nil).domain?
192 # # => true
193 #
194 # @see #subdomain?
195 #
196 # @return [Boolean]
197 def domain?
198 !(@tld.nil? || @sld.nil?)
199 end
200
201 # Checks whether <tt>self</tt> looks like a subdomain.
202 #
203 # This method doesn't actually validate the subdomain.
204 # It only checks whether the instance contains
205 # a value for the {#tld}, {#sld} and {#trd} attributes.
206 # If you also want to validate the domain,
207 # use {#valid_subdomain?} instead.
208 #
209 # @example
210 #
211 # PublicSuffix::Domain.new("com").subdomain?
212 # # => false
213 #
214 # PublicSuffix::Domain.new("com", "google").subdomain?
215 # # => false
216 #
217 # PublicSuffix::Domain.new("com", "google", "www").subdomain?
218 # # => true
219 #
220 # # This is an invalid domain, but returns true
221 # # because this method doesn't validate the content.
222 # PublicSuffix::Domain.new("com", "example", nil).subdomain?
223 # # => true
224 #
225 # @see #domain?
226 #
227 # @return [Boolean]
228 def subdomain?
229 !(@tld.nil? || @sld.nil? || @trd.nil?)
230 end
231
232 end
233
234 end
+0
-41
vendor/public_suffix/errors.rb less more
0 # frozen_string_literal: true
1
2 # = Public Suffix
3 #
4 # Domain name parser based on the Public Suffix List.
5 #
6 # Copyright (c) 2009-2019 Simone Carletti <weppos@weppos.net>
7
8 module PublicSuffix
9
10 class Error < StandardError
11 end
12
13 # Raised when trying to parse an invalid name.
14 # A name is considered invalid when no rule is found in the definition list.
15 #
16 # @example
17 #
18 # PublicSuffix.parse("nic.test")
19 # # => PublicSuffix::DomainInvalid
20 #
21 # PublicSuffix.parse("http://www.nic.it")
22 # # => PublicSuffix::DomainInvalid
23 #
24 class DomainInvalid < Error
25 end
26
27 # Raised when trying to parse a name that matches a suffix.
28 #
29 # @example
30 #
31 # PublicSuffix.parse("nic.do")
32 # # => PublicSuffix::DomainNotAllowed
33 #
34 # PublicSuffix.parse("www.nic.do")
35 # # => PublicSuffix::Domain
36 #
37 class DomainNotAllowed < DomainInvalid
38 end
39
40 end
+0
-247
vendor/public_suffix/list.rb less more
0 # frozen_string_literal: true
1
2 # = Public Suffix
3 #
4 # Domain name parser based on the Public Suffix List.
5 #
6 # Copyright (c) 2009-2019 Simone Carletti <weppos@weppos.net>
7
8 module PublicSuffix
9
10 # A {PublicSuffix::List} is a collection of one
11 # or more {PublicSuffix::Rule}.
12 #
13 # Given a {PublicSuffix::List},
14 # you can add or remove {PublicSuffix::Rule},
15 # iterate all items in the list or search for the first rule
16 # which matches a specific domain name.
17 #
18 # # Create a new list
19 # list = PublicSuffix::List.new
20 #
21 # # Push two rules to the list
22 # list << PublicSuffix::Rule.factory("it")
23 # list << PublicSuffix::Rule.factory("com")
24 #
25 # # Get the size of the list
26 # list.size
27 # # => 2
28 #
29 # # Search for the rule matching given domain
30 # list.find("example.com")
31 # # => #<PublicSuffix::Rule::Normal>
32 # list.find("example.org")
33 # # => nil
34 #
35 # You can create as many {PublicSuffix::List} you want.
36 # The {PublicSuffix::List.default} rule list is used
37 # to tokenize and validate a domain.
38 #
39 class List
40
41 DEFAULT_LIST_PATH = File.expand_path("../../data/list.txt", __dir__)
42
43 # Gets the default rule list.
44 #
45 # Initializes a new {PublicSuffix::List} parsing the content
46 # of {PublicSuffix::List.default_list_content}, if required.
47 #
48 # @return [PublicSuffix::List]
49 def self.default(**options)
50 @default ||= parse(File.read(DEFAULT_LIST_PATH), options)
51 end
52
53 # Sets the default rule list to +value+.
54 #
55 # @param value [PublicSuffix::List] the new list
56 # @return [PublicSuffix::List]
57 def self.default=(value)
58 @default = value
59 end
60
61 # Parse given +input+ treating the content as Public Suffix List.
62 #
63 # See http://publicsuffix.org/format/ for more details about input format.
64 #
65 # @param string [#each_line] the list to parse
66 # @param private_domains [Boolean] whether to ignore the private domains section
67 # @return [PublicSuffix::List]
68 def self.parse(input, private_domains: true)
69 comment_token = "//"
70 private_token = "===BEGIN PRIVATE DOMAINS==="
71 section = nil # 1 == ICANN, 2 == PRIVATE
72
73 new do |list|
74 input.each_line do |line|
75 line.strip!
76 case # rubocop:disable Style/EmptyCaseCondition
77
78 # skip blank lines
79 when line.empty?
80 next
81
82 # include private domains or stop scanner
83 when line.include?(private_token)
84 break if !private_domains
85
86 section = 2
87
88 # skip comments
89 when line.start_with?(comment_token)
90 next
91
92 else
93 list.add(Rule.factory(line, private: section == 2))
94
95 end
96 end
97 end
98 end
99
100
101 # Initializes an empty {PublicSuffix::List}.
102 #
103 # @yield [self] Yields on self.
104 # @yieldparam [PublicSuffix::List] self The newly created instance.
105 def initialize
106 @rules = {}
107 yield(self) if block_given?
108 end
109
110
111 # Checks whether two lists are equal.
112 #
113 # List <tt>one</tt> is equal to <tt>two</tt>, if <tt>two</tt> is an instance of
114 # {PublicSuffix::List} and each +PublicSuffix::Rule::*+
115 # in list <tt>one</tt> is available in list <tt>two</tt>, in the same order.
116 #
117 # @param other [PublicSuffix::List] the List to compare
118 # @return [Boolean]
119 def ==(other)
120 return false unless other.is_a?(List)
121
122 equal?(other) || @rules == other.rules
123 end
124 alias eql? ==
125
126 # Iterates each rule in the list.
127 def each(&block)
128 Enumerator.new do |y|
129 @rules.each do |key, node|
130 y << entry_to_rule(node, key)
131 end
132 end.each(&block)
133 end
134
135
136 # Adds the given object to the list and optionally refreshes the rule index.
137 #
138 # @param rule [PublicSuffix::Rule::*] the rule to add to the list
139 # @return [self]
140 def add(rule)
141 @rules[rule.value] = rule_to_entry(rule)
142 self
143 end
144 alias << add
145
146 # Gets the number of rules in the list.
147 #
148 # @return [Integer]
149 def size
150 @rules.size
151 end
152
153 # Checks whether the list is empty.
154 #
155 # @return [Boolean]
156 def empty?
157 @rules.empty?
158 end
159
160 # Removes all rules.
161 #
162 # @return [self]
163 def clear
164 @rules.clear
165 self
166 end
167
168 # Finds and returns the rule corresponding to the longest public suffix for the hostname.
169 #
170 # @param name [#to_s] the hostname
171 # @param default [PublicSuffix::Rule::*] the default rule to return in case no rule matches
172 # @return [PublicSuffix::Rule::*]
173 def find(name, default: default_rule, **options)
174 rule = select(name, **options).inject do |l, r|
175 return r if r.class == Rule::Exception
176
177 l.length > r.length ? l : r
178 end
179 rule || default
180 end
181
182 # Selects all the rules matching given hostame.
183 #
184 # If `ignore_private` is set to true, the algorithm will skip the rules that are flagged as
185 # private domain. Note that the rules will still be part of the loop.
186 # If you frequently need to access lists ignoring the private domains,
187 # you should create a list that doesn't include these domains setting the
188 # `private_domains: false` option when calling {.parse}.
189 #
190 # Note that this method is currently private, as you should not rely on it. Instead,
191 # the public interface is {#find}. The current internal algorithm allows to return all
192 # matching rules, but different data structures may not be able to do it, and instead would
193 # return only the match. For this reason, you should rely on {#find}.
194 #
195 # @param name [#to_s] the hostname
196 # @param ignore_private [Boolean]
197 # @return [Array<PublicSuffix::Rule::*>]
198 def select(name, ignore_private: false)
199 name = name.to_s
200
201 parts = name.split(DOT).reverse!
202 index = 0
203 query = parts[index]
204 rules = []
205
206 loop do
207 match = @rules[query]
208 rules << entry_to_rule(match, query) if !match.nil? && (ignore_private == false || match.private == false)
209
210 index += 1
211 break if index >= parts.size
212
213 query = parts[index] + DOT + query
214 end
215
216 rules
217 end
218 private :select # rubocop:disable Style/AccessModifierDeclarations
219
220 # Gets the default rule.
221 #
222 # @see PublicSuffix::Rule.default_rule
223 #
224 # @return [PublicSuffix::Rule::*]
225 def default_rule
226 PublicSuffix::Rule.default
227 end
228
229
230 protected
231
232 attr_reader :rules
233
234
235 private
236
237 def entry_to_rule(entry, value)
238 entry.type.new(value: value, length: entry.length, private: entry.private)
239 end
240
241 def rule_to_entry(rule)
242 Rule::Entry.new(rule.class, rule.length, rule.private)
243 end
244
245 end
246 end
+0
-350
vendor/public_suffix/rule.rb less more
0 # frozen_string_literal: true
1
2 # = Public Suffix
3 #
4 # Domain name parser based on the Public Suffix List.
5 #
6 # Copyright (c) 2009-2019 Simone Carletti <weppos@weppos.net>
7
8 module PublicSuffix
9
10 # A Rule is a special object which holds a single definition
11 # of the Public Suffix List.
12 #
13 # There are 3 types of rules, each one represented by a specific
14 # subclass within the +PublicSuffix::Rule+ namespace.
15 #
16 # To create a new Rule, use the {PublicSuffix::Rule#factory} method.
17 #
18 # PublicSuffix::Rule.factory("ar")
19 # # => #<PublicSuffix::Rule::Normal>
20 #
21 module Rule
22
23 # @api internal
24 Entry = Struct.new(:type, :length, :private)
25
26 # = Abstract rule class
27 #
28 # This represent the base class for a Rule definition
29 # in the {Public Suffix List}[https://publicsuffix.org].
30 #
31 # This is intended to be an Abstract class
32 # and you shouldn't create a direct instance. The only purpose
33 # of this class is to expose a common interface
34 # for all the available subclasses.
35 #
36 # * {PublicSuffix::Rule::Normal}
37 # * {PublicSuffix::Rule::Exception}
38 # * {PublicSuffix::Rule::Wildcard}
39 #
40 # ## Properties
41 #
42 # A rule is composed by 4 properties:
43 #
44 # value - A normalized version of the rule name.
45 # The normalization process depends on rule tpe.
46 #
47 # Here's an example
48 #
49 # PublicSuffix::Rule.factory("*.google.com")
50 # #<PublicSuffix::Rule::Wildcard:0x1015c14b0
51 # @value="google.com"
52 # >
53 #
54 # ## Rule Creation
55 #
56 # The best way to create a new rule is passing the rule name
57 # to the <tt>PublicSuffix::Rule.factory</tt> method.
58 #
59 # PublicSuffix::Rule.factory("com")
60 # # => PublicSuffix::Rule::Normal
61 #
62 # PublicSuffix::Rule.factory("*.com")
63 # # => PublicSuffix::Rule::Wildcard
64 #
65 # This method will detect the rule type and create an instance
66 # from the proper rule class.
67 #
68 # ## Rule Usage
69 #
70 # A rule describes the composition of a domain name and explains how to tokenize
71 # the name into tld, sld and trd.
72 #
73 # To use a rule, you first need to be sure the name you want to tokenize
74 # can be handled by the current rule.
75 # You can use the <tt>#match?</tt> method.
76 #
77 # rule = PublicSuffix::Rule.factory("com")
78 #
79 # rule.match?("google.com")
80 # # => true
81 #
82 # rule.match?("google.com")
83 # # => false
84 #
85 # Rule order is significant. A name can match more than one rule.
86 # See the {Public Suffix Documentation}[http://publicsuffix.org/format/]
87 # to learn more about rule priority.
88 #
89 # When you have the right rule, you can use it to tokenize the domain name.
90 #
91 # rule = PublicSuffix::Rule.factory("com")
92 #
93 # rule.decompose("google.com")
94 # # => ["google", "com"]
95 #
96 # rule.decompose("www.google.com")
97 # # => ["www.google", "com"]
98 #
99 # @abstract
100 #
101 class Base
102
103 # @return [String] the rule definition
104 attr_reader :value
105
106 # @return [String] the length of the rule
107 attr_reader :length
108
109 # @return [Boolean] true if the rule is a private domain
110 attr_reader :private
111
112
113 # Initializes a new rule from the content.
114 #
115 # @param content [String] the content of the rule
116 # @param private [Boolean]
117 def self.build(content, private: false)
118 new(value: content, private: private)
119 end
120
121 # Initializes a new rule.
122 #
123 # @param value [String]
124 # @param private [Boolean]
125 def initialize(value:, length: nil, private: false)
126 @value = value.to_s
127 @length = length || @value.count(DOT) + 1
128 @private = private
129 end
130
131 # Checks whether this rule is equal to <tt>other</tt>.
132 #
133 # @param [PublicSuffix::Rule::*] other The rule to compare
134 # @return [Boolean]
135 # Returns true if this rule and other are instances of the same class
136 # and has the same value, false otherwise.
137 def ==(other)
138 equal?(other) || (self.class == other.class && value == other.value)
139 end
140 alias eql? ==
141
142 # Checks if this rule matches +name+.
143 #
144 # A domain name is said to match a rule if and only if
145 # all of the following conditions are met:
146 #
147 # - When the domain and rule are split into corresponding labels,
148 # that the domain contains as many or more labels than the rule.
149 # - Beginning with the right-most labels of both the domain and the rule,
150 # and continuing for all labels in the rule, one finds that for every pair,
151 # either they are identical, or that the label from the rule is "*".
152 #
153 # @see https://publicsuffix.org/list/
154 #
155 # @example
156 # PublicSuffix::Rule.factory("com").match?("example.com")
157 # # => true
158 # PublicSuffix::Rule.factory("com").match?("example.net")
159 # # => false
160 #
161 # @param name [String] the domain name to check
162 # @return [Boolean]
163 def match?(name)
164 # Note: it works because of the assumption there are no
165 # rules like foo.*.com. If the assumption is incorrect,
166 # we need to properly walk the input and skip parts according
167 # to wildcard component.
168 diff = name.chomp(value)
169 diff.empty? || diff.end_with?(DOT)
170 end
171
172 # @abstract
173 def parts
174 raise NotImplementedError
175 end
176
177 # @abstract
178 # @param [String, #to_s] name The domain name to decompose
179 # @return [Array<String, nil>]
180 def decompose(*)
181 raise NotImplementedError
182 end
183
184 end
185
186 # Normal represents a standard rule (e.g. com).
187 class Normal < Base
188
189 # Gets the original rule definition.
190 #
191 # @return [String] The rule definition.
192 def rule
193 value
194 end
195
196 # Decomposes the domain name according to rule properties.
197 #
198 # @param [String, #to_s] name The domain name to decompose
199 # @return [Array<String>] The array with [trd + sld, tld].
200 def decompose(domain)
201 suffix = parts.join('\.')
202 matches = domain.to_s.match(/^(.*)\.(#{suffix})$/)
203 matches ? matches[1..2] : [nil, nil]
204 end
205
206 # dot-split rule value and returns all rule parts
207 # in the order they appear in the value.
208 #
209 # @return [Array<String>]
210 def parts
211 @value.split(DOT)
212 end
213
214 end
215
216 # Wildcard represents a wildcard rule (e.g. *.co.uk).
217 class Wildcard < Base
218
219 # Initializes a new rule from the content.
220 #
221 # @param content [String] the content of the rule
222 # @param private [Boolean]
223 def self.build(content, private: false)
224 new(value: content.to_s[2..-1], private: private)
225 end
226
227 # Initializes a new rule.
228 #
229 # @param value [String]
230 # @param private [Boolean]
231 def initialize(value:, length: nil, private: false)
232 super(value: value, length: length, private: private)
233 length or @length += 1 # * counts as 1
234 end
235
236 # Gets the original rule definition.
237 #
238 # @return [String] The rule definition.
239 def rule
240 value == "" ? STAR : STAR + DOT + value
241 end
242
243 # Decomposes the domain name according to rule properties.
244 #
245 # @param [String, #to_s] name The domain name to decompose
246 # @return [Array<String>] The array with [trd + sld, tld].
247 def decompose(domain)
248 suffix = ([".*?"] + parts).join('\.')
249 matches = domain.to_s.match(/^(.*)\.(#{suffix})$/)
250 matches ? matches[1..2] : [nil, nil]
251 end
252
253 # dot-split rule value and returns all rule parts
254 # in the order they appear in the value.
255 #
256 # @return [Array<String>]
257 def parts
258 @value.split(DOT)
259 end
260
261 end
262
263 # Exception represents an exception rule (e.g. !parliament.uk).
264 class Exception < Base
265
266 # Initializes a new rule from the content.
267 #
268 # @param content [String] the content of the rule
269 # @param private [Boolean]
270 def self.build(content, private: false)
271 new(value: content.to_s[1..-1], private: private)
272 end
273
274 # Gets the original rule definition.
275 #
276 # @return [String] The rule definition.
277 def rule
278 BANG + value
279 end
280
281 # Decomposes the domain name according to rule properties.
282 #
283 # @param [String, #to_s] name The domain name to decompose
284 # @return [Array<String>] The array with [trd + sld, tld].
285 def decompose(domain)
286 suffix = parts.join('\.')
287 matches = domain.to_s.match(/^(.*)\.(#{suffix})$/)
288 matches ? matches[1..2] : [nil, nil]
289 end
290
291 # dot-split rule value and returns all rule parts
292 # in the order they appear in the value.
293 # The leftmost label is not considered a label.
294 #
295 # See http://publicsuffix.org/format/:
296 # If the prevailing rule is a exception rule,
297 # modify it by removing the leftmost label.
298 #
299 # @return [Array<String>]
300 def parts
301 @value.split(DOT)[1..-1]
302 end
303
304 end
305
306
307 # Takes the +name+ of the rule, detects the specific rule class
308 # and creates a new instance of that class.
309 # The +name+ becomes the rule +value+.
310 #
311 # @example Creates a Normal rule
312 # PublicSuffix::Rule.factory("ar")
313 # # => #<PublicSuffix::Rule::Normal>
314 #
315 # @example Creates a Wildcard rule
316 # PublicSuffix::Rule.factory("*.ar")
317 # # => #<PublicSuffix::Rule::Wildcard>
318 #
319 # @example Creates an Exception rule
320 # PublicSuffix::Rule.factory("!congresodelalengua3.ar")
321 # # => #<PublicSuffix::Rule::Exception>
322 #
323 # @param [String] content The rule content.
324 # @return [PublicSuffix::Rule::*] A rule instance.
325 def self.factory(content, private: false)
326 case content.to_s[0, 1]
327 when STAR
328 Wildcard
329 when BANG
330 Exception
331 else
332 Normal
333 end.build(content, private: private)
334 end
335
336 # The default rule to use if no rule match.
337 #
338 # The default rule is "*". From https://publicsuffix.org/list/:
339 #
340 # > If no rules match, the prevailing rule is "*".
341 #
342 # @return [PublicSuffix::Rule::Wildcard] The default rule.
343 def self.default
344 factory(STAR)
345 end
346
347 end
348
349 end
+0
-13
vendor/public_suffix/version.rb less more
0 # frozen_string_literal: true
1
2 #
3 # = Public Suffix
4 #
5 # Domain name parser based on the Public Suffix List.
6 #
7 # Copyright (c) 2009-2019 Simone Carletti <weppos@weppos.net>
8
9 module PublicSuffix
10 # The current library version.
11 VERSION = "3.1.1"
12 end
+0
-179
vendor/public_suffix.rb less more
0 # frozen_string_literal: true
1
2 # = Public Suffix
3 #
4 # Domain name parser based on the Public Suffix List.
5 #
6 # Copyright (c) 2009-2019 Simone Carletti <weppos@weppos.net>
7
8 require_relative "public_suffix/domain"
9 require_relative "public_suffix/version"
10 require_relative "public_suffix/errors"
11 require_relative "public_suffix/rule"
12 require_relative "public_suffix/list"
13
14 # PublicSuffix is a Ruby domain name parser based on the Public Suffix List.
15 #
16 # The [Public Suffix List](https://publicsuffix.org) is a cross-vendor initiative
17 # to provide an accurate list of domain name suffixes.
18 #
19 # The Public Suffix List is an initiative of the Mozilla Project,
20 # but is maintained as a community resource. It is available for use in any software,
21 # but was originally created to meet the needs of browser manufacturers.
22 module PublicSuffix
23
24 DOT = "."
25 BANG = "!"
26 STAR = "*"
27
28 # Parses +name+ and returns the {PublicSuffix::Domain} instance.
29 #
30 # @example Parse a valid domain
31 # PublicSuffix.parse("google.com")
32 # # => #<PublicSuffix::Domain:0x007fec2e51e588 @sld="google", @tld="com", @trd=nil>
33 #
34 # @example Parse a valid subdomain
35 # PublicSuffix.parse("www.google.com")
36 # # => #<PublicSuffix::Domain:0x007fec276d4cf8 @sld="google", @tld="com", @trd="www">
37 #
38 # @example Parse a fully qualified domain
39 # PublicSuffix.parse("google.com.")
40 # # => #<PublicSuffix::Domain:0x007fec257caf38 @sld="google", @tld="com", @trd=nil>
41 #
42 # @example Parse a fully qualified domain (subdomain)
43 # PublicSuffix.parse("www.google.com.")
44 # # => #<PublicSuffix::Domain:0x007fec27b6bca8 @sld="google", @tld="com", @trd="www">
45 #
46 # @example Parse an invalid (unlisted) domain
47 # PublicSuffix.parse("x.yz")
48 # # => #<PublicSuffix::Domain:0x007fec2f49bec0 @sld="x", @tld="yz", @trd=nil>
49 #
50 # @example Parse an invalid (unlisted) domain with strict checking (without applying the default * rule)
51 # PublicSuffix.parse("x.yz", default_rule: nil)
52 # # => PublicSuffix::DomainInvalid: `x.yz` is not a valid domain
53 #
54 # @example Parse an URL (not supported, only domains)
55 # PublicSuffix.parse("http://www.google.com")
56 # # => PublicSuffix::DomainInvalid: http://www.google.com is not expected to contain a scheme
57 #
58 #
59 # @param [String, #to_s] name The domain name or fully qualified domain name to parse.
60 # @param [PublicSuffix::List] list The rule list to search, defaults to the default {PublicSuffix::List}
61 # @param [Boolean] ignore_private
62 # @return [PublicSuffix::Domain]
63 #
64 # @raise [PublicSuffix::DomainInvalid]
65 # If domain is not a valid domain.
66 # @raise [PublicSuffix::DomainNotAllowed]
67 # If a rule for +domain+ is found, but the rule doesn't allow +domain+.
68 def self.parse(name, list: List.default, default_rule: list.default_rule, ignore_private: false)
69 what = normalize(name)
70 raise what if what.is_a?(DomainInvalid)
71
72 rule = list.find(what, default: default_rule, ignore_private: ignore_private)
73
74 # rubocop:disable Style/IfUnlessModifier
75 if rule.nil?
76 raise DomainInvalid, "`#{what}` is not a valid domain"
77 end
78 if rule.decompose(what).last.nil?
79 raise DomainNotAllowed, "`#{what}` is not allowed according to Registry policy"
80 end
81
82 # rubocop:enable Style/IfUnlessModifier
83
84 decompose(what, rule)
85 end
86
87 # Checks whether +domain+ is assigned and allowed, without actually parsing it.
88 #
89 # This method doesn't care whether domain is a domain or subdomain.
90 # The validation is performed using the default {PublicSuffix::List}.
91 #
92 # @example Validate a valid domain
93 # PublicSuffix.valid?("example.com")
94 # # => true
95 #
96 # @example Validate a valid subdomain
97 # PublicSuffix.valid?("www.example.com")
98 # # => true
99 #
100 # @example Validate a not-listed domain
101 # PublicSuffix.valid?("example.tldnotlisted")
102 # # => true
103 #
104 # @example Validate a not-listed domain with strict checking (without applying the default * rule)
105 # PublicSuffix.valid?("example.tldnotlisted")
106 # # => true
107 # PublicSuffix.valid?("example.tldnotlisted", default_rule: nil)
108 # # => false
109 #
110 # @example Validate a fully qualified domain
111 # PublicSuffix.valid?("google.com.")
112 # # => true
113 # PublicSuffix.valid?("www.google.com.")
114 # # => true
115 #
116 # @example Check an URL (which is not a valid domain)
117 # PublicSuffix.valid?("http://www.example.com")
118 # # => false
119 #
120 #
121 # @param [String, #to_s] name The domain name or fully qualified domain name to validate.
122 # @param [Boolean] ignore_private
123 # @return [Boolean]
124 def self.valid?(name, list: List.default, default_rule: list.default_rule, ignore_private: false)
125 what = normalize(name)
126 return false if what.is_a?(DomainInvalid)
127
128 rule = list.find(what, default: default_rule, ignore_private: ignore_private)
129
130 !rule.nil? && !rule.decompose(what).last.nil?
131 end
132
133 # Attempt to parse the name and returns the domain, if valid.
134 #
135 # This method doesn't raise. Instead, it returns nil if the domain is not valid for whatever reason.
136 #
137 # @param [String, #to_s] name The domain name or fully qualified domain name to parse.
138 # @param [PublicSuffix::List] list The rule list to search, defaults to the default {PublicSuffix::List}
139 # @param [Boolean] ignore_private
140 # @return [String]
141 def self.domain(name, **options)
142 parse(name, **options).domain
143 rescue PublicSuffix::Error
144 nil
145 end
146
147
148 # private
149
150 def self.decompose(name, rule)
151 left, right = rule.decompose(name)
152
153 parts = left.split(DOT)
154 # If we have 0 parts left, there is just a tld and no domain or subdomain
155 # If we have 1 part left, there is just a tld, domain and not subdomain
156 # If we have 2 parts left, the last part is the domain, the other parts (combined) are the subdomain
157 tld = right
158 sld = parts.empty? ? nil : parts.pop
159 trd = parts.empty? ? nil : parts.join(DOT)
160
161 Domain.new(tld, sld, trd)
162 end
163
164 # Pretend we know how to deal with user input.
165 def self.normalize(name)
166 name = name.to_s.dup
167 name.strip!
168 name.chomp!(DOT)
169 name.downcase!
170
171 return DomainInvalid.new("Name is blank") if name.empty?
172 return DomainInvalid.new("Name starts with a dot") if name.start_with?(DOT)
173 return DomainInvalid.new("%s is not expected to contain a scheme" % name) if name.include?("://")
174
175 name
176 end
177
178 end
+0
-357
vendor/text/double_metaphone.rb less more
0 # encoding: utf-8
1 #
2 # Ruby implementation of the Double Metaphone algorithm by Lawrence Philips,
3 # originally published in the June 2000 issue of C/C++ Users Journal.
4 #
5 # Based on Stephen Woodbridge's PHP version - http://swoodbridge.com/DoubleMetaPhone/
6 #
7 # Author: Tim Fletcher (mail@tfletcher.com)
8 #
9
10 module Text # :nodoc:
11 module Metaphone
12
13 # Returns the primary and secondary double metaphone tokens
14 # (the secondary will be nil if equal to the primary).
15 def double_metaphone(str)
16 primary, secondary, current = [], [], 0
17 original, length, last = "#{str} ".upcase, str.length, str.length - 1
18 if /^GN|KN|PN|WR|PS$/ =~ original[0, 2]
19 current += 1
20 end
21 if 'X' == original[0, 1]
22 primary << :S
23 secondary << :S
24 current += 1
25 end
26 while primary.length < 4 || secondary.length < 4
27 break if current > str.length
28 a, b, c = double_metaphone_lookup(original, current, length, last)
29 primary << a if a
30 secondary << b if b
31 current += c if c
32 end
33 primary, secondary = primary.join("")[0, 4], secondary.join("")[0, 4]
34 return primary, (primary == secondary ? nil : secondary)
35 end
36
37
38 private
39
40 def slavo_germanic?(str)
41 /W|K|CZ|WITZ/ =~ str
42 end
43
44 def vowel?(str)
45 /^A|E|I|O|U|Y$/ =~ str
46 end
47
48 def double_metaphone_lookup(str, pos, length, last)
49 case str[pos, 1]
50 when /^A|E|I|O|U|Y$/
51 if 0 == pos
52 return :A, :A, 1
53 else
54 return nil, nil, 1
55 end
56 when 'B'
57 return :P, :P, ('B' == str[pos + 1, 1] ? 2 : 1)
58 when 'Ç'
59 return :S, :S, 1
60 when 'C'
61 if pos > 1 &&
62 !vowel?(str[pos - 2, 1]) &&
63 'ACH' == str[pos - 1, 3] &&
64 str[pos + 2, 1] != 'I' && (
65 str[pos + 2, 1] != 'E' ||
66 str[pos - 2, 6] =~ /^(B|M)ACHER$/
67 ) then
68 return :K, :K, 2
69 elsif 0 == pos && 'CAESAR' == str[pos, 6]
70 return :S, :S, 2
71 elsif 'CHIA' == str[pos, 4]
72 return :K, :K, 2
73 elsif 'CH' == str[pos, 2]
74 if pos > 0 && 'CHAE' == str[pos, 4]
75 return :K, :X, 2
76 elsif 0 == pos && (
77 ['HARAC', 'HARIS'].include?(str[pos + 1, 5]) ||
78 ['HOR', 'HYM', 'HIA', 'HEM'].include?(str[pos + 1, 3])
79 ) && str[0, 5] != 'CHORE' then
80 return :K, :K, 2
81 elsif ['VAN ','VON '].include?(str[0, 4]) ||
82 'SCH' == str[0, 3] ||
83 ['ORCHES','ARCHIT','ORCHID'].include?(str[pos - 2, 6]) ||
84 ['T','S'].include?(str[pos + 2, 1]) || (
85 ((0 == pos) || ['A','O','U','E'].include?(str[pos - 1, 1])) &&
86 ['L','R','N','M','B','H','F','V','W',' '].include?(str[pos + 2, 1])
87 ) then
88 return :K, :K, 2
89 elsif pos > 0
90 return ('MC' == str[0, 2] ? 'K' : 'X'), 'K', 2
91 else
92 return :X, :X, 2
93 end
94 elsif 'CZ' == str[pos, 2] && 'WICZ' != str[pos - 2, 4]
95 return :S, :X, 2
96 elsif 'CIA' == str[pos + 1, 3]
97 return :X, :X, 3
98 elsif 'CC' == str[pos, 2] && !(1 == pos && 'M' == str[0, 1])
99 if /^I|E|H$/ =~ str[pos + 2, 1] && 'HU' != str[pos + 2, 2]
100 if (1 == pos && 'A' == str[pos - 1, 1]) ||
101 /^UCCE(E|S)$/ =~ str[pos - 1, 5] then
102 return :KS, :KS, 3
103 else
104 return :X, :X, 3
105 end
106 else
107 return :K, :K, 2
108 end
109 elsif /^C(K|G|Q)$/ =~ str[pos, 2]
110 return :K, :K, 2
111 elsif /^C(I|E|Y)$/ =~ str[pos, 2]
112 return :S, (/^CI(O|E|A)$/ =~ str[pos, 3] ? :X : :S), 2
113 else
114 if /^ (C|Q|G)$/ =~ str[pos + 1, 2]
115 return :K, :K, 3
116 else
117 return :K, :K, (/^C|K|Q$/ =~ str[pos + 1, 1] && !(['CE','CI'].include?(str[pos + 1, 2])) ? 2 : 1)
118 end
119 end
120 when 'D'
121 if 'DG' == str[pos, 2]
122 if /^I|E|Y$/ =~ str[pos + 2, 1]
123 return :J, :J, 3
124 else
125 return :TK, :TK, 2
126 end
127 else
128 return :T, :T, (/^D(T|D)$/ =~ str[pos, 2] ? 2 : 1)
129 end
130 when 'F'
131 return :F, :F, ('F' == str[pos + 1, 1] ? 2 : 1)
132 when 'G'
133 if 'H' == str[pos + 1, 1]
134 if pos > 0 && !vowel?(str[pos - 1, 1])
135 return :K, :K, 2
136 elsif 0 == pos
137 if 'I' == str[pos + 2, 1]
138 return :J, :J, 2
139 else
140 return :K, :K, 2
141 end
142 elsif (pos > 1 && /^B|H|D$/ =~ str[pos - 2, 1]) ||
143 (pos > 2 && /^B|H|D$/ =~ str[pos - 3, 1]) ||
144 (pos > 3 && /^B|H$/ =~ str[pos - 4, 1])
145 return nil, nil, 2
146 else
147 if (pos > 2 && 'U' == str[pos - 1, 1] && /^C|G|L|R|T$/ =~ str[pos - 3, 1])
148 return :F, :F, 2
149 elsif pos > 0 && 'I' != str[pos - 1, 1]
150 return :K, :K, 2
151 else
152 return nil, nil, 2
153 end
154 end
155 elsif 'N' == str[pos + 1, 1]
156 if 1 == pos && vowel?(str[0, 1]) && !slavo_germanic?(str)
157 return :KN, :N, 2
158 else
159 if 'EY' != str[pos + 2, 2] && 'Y' != str[pos + 1, 1] && !slavo_germanic?(str)
160 return :N, :KN, 2
161 else
162 return :KN, :KN, 2
163 end
164 end
165 elsif 'LI' == str[pos + 1, 2] && !slavo_germanic?(str)
166 return :KL, :L, 2
167 elsif 0 == pos && ('Y' == str[pos + 1, 1] || /^(E(S|P|B|L|Y|I|R)|I(B|L|N|E))$/ =~ str[pos + 1, 2])
168 return :K, :J, 2
169 elsif (('ER' == str[pos + 1, 2] || 'Y' == str[pos + 1, 1]) &&
170 /^(D|R|M)ANGER$/ !~ str[0, 6] &&
171 /^E|I$/ !~ str[pos - 1, 1] &&
172 /^(R|O)GY$/ !~ str[pos - 1, 3])
173 return :K, :J, 2
174 elsif /^E|I|Y$/ =~ str[pos + 1, 1] || /^(A|O)GGI$/ =~ str[pos - 1, 4]
175 if (/^V(A|O)N $/ =~ str[0, 4] || 'SCH' == str[0, 3]) || 'ET' == str[pos + 1, 2]
176 return :K, :K, 2
177 else
178 if 'IER ' == str[pos + 1, 4]
179 return :J, :J, 2
180 else
181 return :J, :K, 2
182 end
183 end
184 elsif 'G' == str[pos + 1, 1]
185 return :K, :K, 2
186 else
187 return :K, :K, 1
188 end
189 when 'H'
190 if (0 == pos || vowel?(str[pos - 1, 1])) && vowel?(str[pos + 1, 1])
191 return :H, :H, 2
192 else
193 return nil, nil, 1
194 end
195 when 'J'
196 if 'JOSE' == str[pos, 4] || 'SAN ' == str[0, 4]
197 if (0 == pos && ' ' == str[pos + 4, 1]) || 'SAN ' == str[0, 4]
198 return :H, :H, 1
199 else
200 return :J, :H, 1
201 end
202 else
203 current = ('J' == str[pos + 1, 1] ? 2 : 1)
204
205 if 0 == pos && 'JOSE' != str[pos, 4]
206 return :J, :A, current
207 else
208 if vowel?(str[pos - 1, 1]) && !slavo_germanic?(str) && /^A|O$/ =~ str[pos + 1, 1]
209 return :J, :H, current
210 else
211 if last == pos
212 return :J, nil, current
213 else
214 if /^L|T|K|S|N|M|B|Z$/ !~ str[pos + 1, 1] && /^S|K|L$/ !~ str[pos - 1, 1]
215 return :J, :J, current
216 else
217 return nil, nil, current
218 end
219 end
220 end
221 end
222 end
223 when 'K'
224 return :K, :K, ('K' == str[pos + 1, 1] ? 2 : 1)
225 when 'L'
226 if 'L' == str[pos + 1, 1]
227 if (((length - 3) == pos && /^(ILL(O|A)|ALLE)$/ =~ str[pos - 1, 4]) ||
228 ((/^(A|O)S$/ =~ str[last - 1, 2] || /^A|O$/ =~ str[last, 1]) && 'ALLE' == str[pos - 1, 4]))
229 return :L, nil, 2
230 else
231 return :L, :L, 2
232 end
233 else
234 return :L, :L, 1
235 end
236 when 'M'
237 if ('UMB' == str[pos - 1, 3] &&
238 ((last - 1) == pos || 'ER' == str[pos + 2, 2])) || 'M' == str[pos + 1, 1]
239 return :M, :M, 2
240 else
241 return :M, :M, 1
242 end
243 when 'N'
244 return :N, :N, ('N' == str[pos + 1, 1] ? 2 : 1)
245 when 'Ñ'
246 return :N, :N, 1
247 when 'P'
248 if 'H' == str[pos + 1, 1]
249 return :F, :F, 2
250 else
251 return :P, :P, (/^P|B$/ =~ str[pos + 1, 1] ? 2 : 1)
252 end
253 when 'Q'
254 return :K, :K, ('Q' == str[pos + 1, 1] ? 2 : 1)
255 when 'R'
256 current = ('R' == str[pos + 1, 1] ? 2 : 1)
257
258 if last == pos && !slavo_germanic?(str) && 'IE' == str[pos - 2, 2] && /^M(E|A)$/ !~ str[pos - 4, 2]
259 return nil, :R, current
260 else
261 return :R, :R, current
262 end
263 when 'S'
264 if /^(I|Y)SL$/ =~ str[pos - 1, 3]
265 return nil, nil, 1
266 elsif 0 == pos && 'SUGAR' == str[pos, 5]
267 return :X, :S, 1
268 elsif 'SH' == str[pos, 2]
269 if /^H(EIM|OEK|OLM|OLZ)$/ =~ str[pos + 1, 4]
270 return :S, :S, 2
271 else
272 return :X, :X, 2
273 end
274 elsif /^SI(O|A)$/ =~ str[pos, 3] || 'SIAN' == str[pos, 4]
275 return :S, (slavo_germanic?(str) ? :S : :X), 3
276 elsif (0 == pos && /^M|N|L|W$/ =~ str[pos + 1, 1]) || 'Z' == str[pos + 1, 1]
277 return :S, :X, ('Z' == str[pos + 1, 1] ? 2 : 1)
278 elsif 'SC' == str[pos, 2]
279 if 'H' == str[pos + 2, 1]
280 if /^OO|ER|EN|UY|ED|EM$/ =~ str[pos + 3, 2]
281 return (/^E(R|N)$/ =~ str[pos + 3, 2] ? :X : :SK), :SK, 3
282 else
283 return :X, ((0 == pos && !vowel?(str[3, 1]) && ('W' != str[pos + 3, 1])) ? :S : :X), 3
284 end
285 elsif /^I|E|Y$/ =~ str[pos + 2, 1]
286 return :S, :S, 3
287 else
288 return :SK, :SK, 3
289 end
290 else
291 return (last == pos && /^(A|O)I$/ =~ str[pos - 2, 2] ? nil : 'S'), 'S', (/^S|Z$/ =~ str[pos + 1, 1] ? 2 : 1)
292 end
293 when 'T'
294 if 'TION' == str[pos, 4]
295 return :X, :X, 3
296 elsif /^T(IA|CH)$/ =~ str[pos, 3]
297 return :X, :X, 3
298 elsif 'TH' == str[pos, 2] || 'TTH' == str[pos, 3]
299 if /^(O|A)M$/ =~ str[pos + 2, 2] || /^V(A|O)N $/ =~ str[0, 4] || 'SCH' == str[0, 3]
300 return :T, :T, 2
301 else
302 return 0, :T, 2
303 end
304 else
305 return :T, :T, (/^T|D$/ =~ str[pos + 1, 1] ? 2 : 1)
306 end
307 when 'V'
308 return :F, :F, ('V' == str[pos + 1, 1] ? 2 : 1)
309 when 'W'
310 if 'WR' == str[pos, 2]
311 return :R, :R, 2
312 end
313 pri, sec = nil, nil
314
315 if 0 == pos && (vowel?(str[pos + 1, 1]) || 'WH' == str[pos, 2])
316 pri = :A
317 sec = vowel?(str[pos + 1, 1]) ? :F : :A
318 end
319
320 if (last == pos && vowel?(str[pos - 1, 1])) || 'SCH' == str[0, 3] ||
321 /^EWSKI|EWSKY|OWSKI|OWSKY$/ =~ str[pos - 1, 5]
322 return pri, "#{sec}F".intern, 1
323 elsif /^WI(C|T)Z$/ =~ str[pos, 4]
324 return "#{pri}TS".intern, "#{sec}FX".intern, 4
325 else
326 return pri, sec, 1
327 end
328 when 'X'
329 current = (/^C|X$/ =~ str[pos + 1, 1] ? 2 : 1)
330
331 if !(last == pos && (/^(I|E)AU$/ =~ str[pos - 3, 3] || /^(A|O)U$/ =~ str[pos - 2, 2]))
332 return :KS, :KS, current
333 else
334 return nil, nil, current
335 end
336 when 'Z'
337 if 'H' == str[pos + 1, 1]
338 return :J, :J, 2
339 else
340 current = ('Z' == str[pos + 1, 1] ? 2 : 1)
341
342 if /^Z(O|I|A)$/ =~ str[pos + 1, 2] || (slavo_germanic?(str) && (pos > 0 && 'T' != str[pos - 1, 1]))
343 return :S, :TS, current
344 else
345 return :S, :S, current
346 end
347 end
348 else
349 return nil, nil, 1
350 end
351 end # def double_metaphone_lookup
352
353 extend self
354
355 end # module Metaphone
356 end # module Text
+0
-159
vendor/text/levenshtein.rb less more
0 #
1 # Levenshtein distance algorithm implementation for Ruby, with UTF-8 support.
2 #
3 # The Levenshtein distance is a measure of how similar two strings s and t are,
4 # calculated as the number of deletions/insertions/substitutions needed to
5 # transform s into t. The greater the distance, the more the strings differ.
6 #
7 # The Levenshtein distance is also sometimes referred to as the
8 # easier-to-pronounce-and-spell 'edit distance'.
9 #
10 # Author: Paul Battley (pbattley@gmail.com)
11 #
12
13 module Text # :nodoc:
14 module Levenshtein
15
16 # Calculate the Levenshtein distance between two strings +str1+ and +str2+.
17 #
18 # The optional argument max_distance can reduce the number of iterations by
19 # stopping if the Levenshtein distance exceeds this value. This increases
20 # performance where it is only necessary to compare the distance with a
21 # reference value instead of calculating the exact distance.
22 #
23 # The distance is calculated in terms of Unicode codepoints. Be aware that
24 # this algorithm does not perform normalisation: if there is a possibility
25 # of different normalised forms being used, normalisation should be performed
26 # beforehand.
27 #
28 def distance(str1, str2, max_distance = nil)
29 if max_distance
30 distance_with_maximum(str1, str2, max_distance)
31 else
32 distance_without_maximum(str1, str2)
33 end
34 end
35
36 private
37 def distance_with_maximum(str1, str2, max_distance) # :nodoc:
38 s = str1.encode(Encoding::UTF_8).unpack("U*")
39 t = str2.encode(Encoding::UTF_8).unpack("U*")
40
41 n = s.length
42 m = t.length
43 big_int = n * m
44
45 # Swap if necessary so that s is always the shorter of the two strings
46 s, t, n, m = t, s, m, n if m < n
47
48 # If the length difference is already greater than the max_distance, then
49 # there is nothing else to check
50 if (n - m).abs >= max_distance
51 return max_distance
52 end
53
54 return 0 if s == t
55 return m if n.zero?
56 return n if m.zero?
57
58 # The values necessary for our threshold are written; the ones after must
59 # be filled with large integers since the tailing member of the threshold
60 # window in the bottom array will run min across them
61 d = (m + 1).times.map { |i|
62 if i < m || i < max_distance + 1
63 i
64 else
65 big_int
66 end
67 }
68 x = nil
69 e = nil
70
71 n.times do |i|
72 # Since we're reusing arrays, we need to be sure to wipe the value left
73 # of the starting index; we don't have to worry about the value above the
74 # ending index as the arrays were initially filled with large integers
75 # and we progress to the right
76 if e.nil?
77 e = i + 1
78 else
79 e = big_int
80 end
81
82 diag_index = t.length - s.length + i
83
84 # If max_distance was specified, we can reduce second loop. So we set
85 # up our threshold window.
86 # See:
87 # Gusfield, Dan (1997). Algorithms on strings, trees, and sequences:
88 # computer science and computational biology.
89 # Cambridge, UK: Cambridge University Press. ISBN 0-521-58519-8.
90 # pp. 263–264.
91 min = i - max_distance - 1
92 min = 0 if min < 0
93 max = i + max_distance
94 max = m - 1 if max > m - 1
95
96 min.upto(max) do |j|
97 # If the diagonal value is already greater than the max_distance
98 # then we can safety return: the diagonal will never go lower again.
99 # See: http://www.levenshtein.net/
100 if j == diag_index && d[j] >= max_distance
101 return max_distance
102 end
103
104 cost = s[i] == t[j] ? 0 : 1
105 insertion = d[j + 1] + 1
106 deletion = e + 1
107 substitution = d[j] + cost
108 x = insertion < deletion ? insertion : deletion
109 x = substitution if substitution < x
110
111 d[j] = e
112 e = x
113 end
114 d[m] = x
115 end
116
117 if x > max_distance
118 return max_distance
119 else
120 return x
121 end
122 end
123
124 def distance_without_maximum(str1, str2) # :nodoc:
125 s = str1.encode(Encoding::UTF_8).unpack("U*")
126 t = str2.encode(Encoding::UTF_8).unpack("U*")
127
128 n = s.length
129 m = t.length
130
131 return m if n.zero?
132 return n if m.zero?
133
134 d = (0..m).to_a
135 x = nil
136
137 n.times do |i|
138 e = i + 1
139 m.times do |j|
140 cost = s[i] == t[j] ? 0 : 1
141 insertion = d[j + 1] + 1
142 deletion = e + 1
143 substitution = d[j] + cost
144 x = insertion < deletion ? insertion : deletion
145 x = substitution if substitution < x
146
147 d[j] = e
148 e = x
149 end
150 d[m] = x
151 end
152
153 return x
154 end
155
156 extend self
157 end
158 end
+0
-97
vendor/text/metaphone.rb less more
0 #
1 # An implementation of the Metaphone phonetic coding system in Ruby.
2 #
3 # Metaphone encodes names into a phonetic form such that similar-sounding names
4 # have the same or similar Metaphone encodings.
5 #
6 # The original system was described by Lawrence Philips in Computer Language
7 # Vol. 7 No. 12, December 1990, pp 39-43.
8 #
9 # As there are multiple implementations of Metaphone, each with their own
10 # quirks, I have based this on my interpretation of the algorithm specification.
11 # Even LP's original BASIC implementation appears to contain bugs (specifically
12 # with the handling of CC and MB), when compared to his explanation of the
13 # algorithm.
14 #
15 # I have also compared this implementation with that found in PHP's standard
16 # library, which appears to mimic the behaviour of LP's original BASIC
17 # implementation. For compatibility, these rules can also be used by passing
18 # :buggy=>true to the methods.
19 #
20 # Author: Paul Battley (pbattley@gmail.com)
21 #
22
23 module Text # :nodoc:
24 module Metaphone
25
26 module Rules # :nodoc:all
27
28 # Metaphone rules. These are simply applied in order.
29 #
30 STANDARD = [
31 # Regexp, replacement
32 [ /([bcdfhjklmnpqrstvwxyz])\1+/,
33 '\1' ], # Remove doubled consonants except g.
34 # [PHP] remove c from regexp.
35 [ /^ae/, 'E' ],
36 [ /^[gkp]n/, 'N' ],
37 [ /^wr/, 'R' ],
38 [ /^x/, 'S' ],
39 [ /^wh/, 'W' ],
40 [ /mb$/, 'M' ], # [PHP] remove $ from regexp.
41 [ /(?!^)sch/, 'SK' ],
42 [ /th/, '0' ],
43 [ /t?ch|sh/, 'X' ],
44 [ /c(?=ia)/, 'X' ],
45 [ /[st](?=i[ao])/, 'X' ],
46 [ /s?c(?=[iey])/, 'S' ],
47 [ /(ck?|q)/, 'K' ],
48 [ /dg(?=[iey])/, 'J' ],
49 [ /d/, 'T' ],
50 [ /g(?=h[^aeiou])/, '' ],
51 [ /gn(ed)?/, 'N' ],
52 [ /([^g]|^)g(?=[iey])/,
53 '\1J' ],
54 [ /g+/, 'K' ],
55 [ /ph/, 'F' ],
56 [ /([aeiou])h(?=\b|[^aeiou])/,
57 '\1' ],
58 [ /[wy](?![aeiou])/, '' ],
59 [ /z/, 'S' ],
60 [ /v/, 'F' ],
61 [ /(?!^)[aeiou]+/, '' ],
62 ]
63
64 # The rules for the 'buggy' alternate implementation used by PHP etc.
65 #
66 BUGGY = STANDARD.dup
67 BUGGY[0] = [ /([bdfhjklmnpqrstvwxyz])\1+/, '\1' ]
68 BUGGY[6] = [ /mb/, 'M' ]
69 end
70
71 # Returns the Metaphone representation of a string. If the string contains
72 # multiple words, each word in turn is converted into its Metaphone
73 # representation. Note that only the letters A-Z are supported, so any
74 # language-specific processing should be done beforehand.
75 #
76 # If the :buggy option is set, alternate 'buggy' rules are used.
77 #
78 def metaphone(str, options={})
79 return str.strip.split(/\s+/).map { |w| metaphone_word(w, options) }.join(' ')
80 end
81
82 private
83
84 def metaphone_word(w, options={})
85 # Normalise case and remove non-ASCII
86 s = w.downcase.gsub(/[^a-z]/, '')
87 # Apply the Metaphone rules
88 rules = options[:buggy] ? Rules::BUGGY : Rules::STANDARD
89 rules.each { |rx, rep| s.gsub!(rx, rep) }
90 return s.upcase
91 end
92
93 extend self
94
95 end
96 end
+0
-171
vendor/text/porter_stemming.rb less more
0 #
1 # This is the Porter Stemming algorithm, ported to Ruby from the
2 # version coded up in Perl. It's easy to follow against the rules
3 # in the original paper in:
4 #
5 # Porter, 1980, An algorithm for suffix stripping, Program, Vol. 14,
6 # no. 3, pp 130-137,
7 #
8 # Taken from http://www.tartarus.org/~martin/PorterStemmer (Public Domain)
9 #
10 module Text # :nodoc:
11 module PorterStemming
12
13 STEP_2_LIST = {
14 'ational' => 'ate', 'tional' => 'tion', 'enci' => 'ence', 'anci' => 'ance',
15 'izer' => 'ize', 'bli' => 'ble',
16 'alli' => 'al', 'entli' => 'ent', 'eli' => 'e', 'ousli' => 'ous',
17 'ization' => 'ize', 'ation' => 'ate',
18 'ator' => 'ate', 'alism' => 'al', 'iveness' => 'ive', 'fulness' => 'ful',
19 'ousness' => 'ous', 'aliti' => 'al',
20 'iviti' => 'ive', 'biliti' => 'ble', 'logi' => 'log'
21 }
22
23 STEP_3_LIST = {
24 'icate' => 'ic', 'ative' => '', 'alize' => 'al', 'iciti' => 'ic',
25 'ical' => 'ic', 'ful' => '', 'ness' => ''
26 }
27
28 SUFFIX_1_REGEXP = /(
29 ational |
30 tional |
31 enci |
32 anci |
33 izer |
34 bli |
35 alli |
36 entli |
37 eli |
38 ousli |
39 ization |
40 ation |
41 ator |
42 alism |
43 iveness |
44 fulness |
45 ousness |
46 aliti |
47 iviti |
48 biliti |
49 logi)$/x
50
51 SUFFIX_2_REGEXP = /(
52 al |
53 ance |
54 ence |
55 er |
56 ic |
57 able |
58 ible |
59 ant |
60 ement |
61 ment |
62 ent |
63 ou |
64 ism |
65 ate |
66 iti |
67 ous |
68 ive |
69 ize)$/x
70
71 C = "[^aeiou]" # consonant
72 V = "[aeiouy]" # vowel
73 CC = "#{C}(?>[^aeiouy]*)" # consonant sequence
74 VV = "#{V}(?>[aeiou]*)" # vowel sequence
75
76 MGR0 = /^(#{CC})?#{VV}#{CC}/o # [cc]vvcc... is m>0
77 MEQ1 = /^(#{CC})?#{VV}#{CC}(#{VV})?$/o # [cc]vvcc[vv] is m=1
78 MGR1 = /^(#{CC})?#{VV}#{CC}#{VV}#{CC}/o # [cc]vvccvvcc... is m>1
79 VOWEL_IN_STEM = /^(#{CC})?#{V}/o # vowel in stem
80
81 def self.stem(word)
82
83 # make a copy of the given object and convert it to a string.
84 word = word.dup.to_str
85
86 return word if word.length < 3
87
88 # now map initial y to Y so that the patterns never treat it as vowel
89 word[0] = 'Y' if word[0] == ?y
90
91 # Step 1a
92 if word =~ /(ss|i)es$/
93 word = $` + $1
94 elsif word =~ /([^s])s$/
95 word = $` + $1
96 end
97
98 # Step 1b
99 if word =~ /eed$/
100 word.chop! if $` =~ MGR0
101 elsif word =~ /(ed|ing)$/
102 stem = $`
103 if stem =~ VOWEL_IN_STEM
104 word = stem
105 case word
106 when /(at|bl|iz)$/ then word << "e"
107 when /([^aeiouylsz])\1$/ then word.chop!
108 when /^#{CC}#{V}[^aeiouwxy]$/o then word << "e"
109 end
110 end
111 end
112
113 if word =~ /y$/
114 stem = $`
115 word = stem + "i" if stem =~ VOWEL_IN_STEM
116 end
117
118 # Step 2
119 if word =~ SUFFIX_1_REGEXP
120 stem = $`
121 suffix = $1
122 # print "stem= " + stem + "\n" + "suffix=" + suffix + "\n"
123 if stem =~ MGR0
124 word = stem + STEP_2_LIST[suffix]
125 end
126 end
127
128 # Step 3
129 if word =~ /(icate|ative|alize|iciti|ical|ful|ness)$/
130 stem = $`
131 suffix = $1
132 if stem =~ MGR0
133 word = stem + STEP_3_LIST[suffix]
134 end
135 end
136
137 # Step 4
138 if word =~ SUFFIX_2_REGEXP
139 stem = $`
140 if stem =~ MGR1
141 word = stem
142 end
143 elsif word =~ /(s|t)(ion)$/
144 stem = $` + $1
145 if stem =~ MGR1
146 word = stem
147 end
148 end
149
150 # Step 5
151 if word =~ /e$/
152 stem = $`
153 if (stem =~ MGR1) ||
154 (stem =~ MEQ1 && stem !~ /^#{CC}#{V}[^aeiouwxy]$/o)
155 word = stem
156 end
157 end
158
159 if word =~ /ll$/ && word =~ MGR1
160 word.chop!
161 end
162
163 # and turn initial Y back to y
164 word[0] = 'y' if word[0] == ?Y
165
166 word
167 end
168
169 end
170 end
+0
-59
vendor/text/soundex.rb less more
0 #
1 # Ruby implementation of the Soundex algorithm,
2 # as described by Knuth in volume 3 of The Art of Computer Programming.
3 #
4 # Author: Michael Neumann (neumann@s-direktnet.de)
5 #
6
7 module Text # :nodoc:
8 module Soundex
9
10 def soundex(str_or_arr)
11 case str_or_arr
12 when String
13 soundex_str(str_or_arr)
14 when Array
15 str_or_arr.collect{|ele| soundex_str(ele)}
16 else
17 nil
18 end
19 end
20 module_function :soundex
21
22 private
23
24 #
25 # returns nil if the value couldn't be calculated (empty-string, wrong-character)
26 # do not change the parameter "str"
27 #
28 def soundex_str(str)
29 str = str.upcase.gsub(/[^A-Z]/, "")
30 return nil if str.empty?
31
32 last_code = get_code(str[0,1])
33 soundex_code = str[0,1]
34
35 for index in 1...(str.size) do
36 return soundex_code if soundex_code.size == 4
37
38 code = get_code(str[index,1])
39
40 if code == "0" then
41 last_code = nil
42 elsif code != last_code then
43 soundex_code += code
44 last_code = code
45 end
46 end # for
47
48 return soundex_code.ljust(4, "0")
49 end
50 module_function :soundex_str
51
52 def get_code(char)
53 char.tr! "AEIOUYWHBPFVCSKGJQXZDTLMNR", "00000000111122222222334556"
54 end
55 module_function :get_code
56
57 end # module Soundex
58 end # module Text
+0
-9
vendor/text/version.rb less more
0 module Text
1 module VERSION #:nodoc:
2 MAJOR = 1
3 MINOR = 3
4 TINY = 1
5
6 STRING = [MAJOR, MINOR, TINY].join('.')
7 end
8 end
+0
-61
vendor/text/white_similarity.rb less more
0 # encoding: utf-8
1 # Original author: Wilker Lúcio <wilkerlucio@gmail.com>
2
3 require "set"
4
5 module Text
6
7 # Ruby implementation of the string similarity described by Simon White
8 # at: http://www.catalysoft.com/articles/StrikeAMatch.html
9 #
10 # 2 * |pairs(s1) INTERSECT pairs(s2)|
11 # similarity(s1, s2) = -----------------------------------
12 # |pairs(s1)| + |pairs(s2)|
13 #
14 # e.g.
15 # 2 * |{FR, NC}|
16 # similarity(FRANCE, FRENCH) = ---------------------------------------
17 # |{FR,RA,AN,NC,CE}| + |{FR,RE,EN,NC,CH}|
18 #
19 # = (2 * 2) / (5 + 5)
20 #
21 # = 0.4
22 #
23 # WhiteSimilarity.new.similarity("FRANCE", "FRENCH")
24 #
25 class WhiteSimilarity
26
27 def self.similarity(str1, str2)
28 new.similarity(str1, str2)
29 end
30
31 def initialize
32 @word_letter_pairs = {}
33 end
34
35 def similarity(str1, str2)
36 pairs1 = word_letter_pairs(str1)
37 pairs2 = word_letter_pairs(str2).dup
38
39 union = pairs1.length + pairs2.length
40
41 intersection = 0
42 pairs1.each do |pair1|
43 if index = pairs2.index(pair1)
44 intersection += 1
45 pairs2.delete_at(index)
46 end
47 end
48
49 (2.0 * intersection) / union
50 end
51
52 private
53 def word_letter_pairs(str)
54 @word_letter_pairs[str] ||=
55 str.upcase.split(/\s+/).map{ |word|
56 (0 ... (word.length - 1)).map { |i| word[i, 2] }
57 }.flatten.freeze
58 end
59 end
60 end
+0
-7
vendor/text.rb less more
0 require 'text/double_metaphone'
1 require 'text/levenshtein'
2 require 'text/metaphone'
3 require 'text/porter_stemming'
4 require 'text/soundex'
5 require 'text/version'
6 require 'text/white_similarity'
vendor/typed-array/.DS_Store less more
Binary diff not shown
+0
-115
vendor/typed-array/functions.rb less more
0 # Provides the validation functions that get included into a TypedArray
1
2 # Namespace TypedArray
3 module TypedArray
4
5 # The functions that get included into TypedArray
6 module Functions
7 # Validates outcome. See Array#initialize
8 def initialize *args, &block
9 ary = Array.new *args, &block
10 self.replace ary
11 end
12
13 # Validates outcome. See Array#replace
14 def replace other_ary
15 _ensure_all_items_in_array_are_allowed other_ary
16 super
17 end
18
19 # Validates outcome. See Array#&
20 def & ary
21 self.class.new super
22 end
23
24 # Validates outcome. See Array#*
25 def * int
26 self.class.new super
27 end
28
29 # Validates outcome. See Array#+
30 def + ary
31 self.class.new super
32 end
33
34 # Validates outcome. See Array#<<
35 def << item
36 _ensure_item_is_allowed item
37 super
38 end
39
40 # Validates outcome. See Array#[]
41 def [] idx
42 self.class.new super
43 end
44
45 # Validates outcome. See Array#slice
46 def slice *args
47 self.class.new super
48 end
49
50 # Validates outcome. See Array#[]=
51 def []= idx, item
52 _ensure_item_is_allowed item
53 super
54 end
55
56 # Validates outcome. See Array#concat
57 def concat other_ary
58 _ensure_all_items_in_array_are_allowed other_ary
59 super
60 end
61
62 # Validates outcome. See Array#eql?
63 def eql? other_ary
64 _ensure_all_items_in_array_are_allowed other_ary
65 super
66 end
67
68 # Validates outcome. See Array#fill
69 def fill *args, &block
70 ary = self.to_a
71 ary.fill *args, &block
72 self.replace ary
73 end
74
75 # Validates outcome. See Array#push
76 def push *items
77 _ensure_all_items_in_array_are_allowed items
78 super
79 end
80
81 # Validates outcome. See Array#unshift
82 def unshift *items
83 _ensure_all_items_in_array_are_allowed items
84 super
85 end
86
87 # Validates outcome. See Array#map!
88 def map! &block
89 self.replace( self.map &block )
90 end
91
92 protected
93
94 # Ensure that all items in the passed Array are allowed
95 def _ensure_all_items_in_array_are_allowed ary
96 # If we're getting an instance of self, accept
97 return true if ary.is_a? self.class
98 _ensure_item_is_allowed( ary, [Array] )
99 ary.each do |item|
100 _ensure_item_is_allowed(item)
101 end
102 end
103
104 # Ensure that the specific item passed is allowed
105 def _ensure_item_is_allowed item, expected=nil
106 return true if item.nil? #allow nil entries
107 expected = self.class.restricted_types if expected.nil?
108 expected.each do |allowed|
109 return true if item.class <= allowed
110 end
111 raise TypedArray::UnexpectedTypeException.new expected, item.class
112 end
113 end
114 end
+0
-76
vendor/typed-array.rb less more
0 # :include: ../README.rdoc
1
2 require "typed-array/functions"
3
4 # Provides TypedArray functionality to a subclass of Array
5 # when extended in the class's definiton
6 module TypedArray
7
8 # Hook the extension process in order to include the necessary functions
9 # and do some basic sanity checks.
10 def self.extended( mod )
11 unless mod <= Array
12 raise UnexpectedTypeException.new( [Array], mod.class )
13 end
14 mod.module_exec(self::Functions) do |functions_module|
15 include functions_module
16 end
17 end
18
19 # when a class inherits from this one, make sure that it also inherits
20 # the types that are being enforced
21 def inherited( subclass )
22 self._subclasses << subclass
23 subclass.restricted_types *restricted_types
24 end
25
26 # A getter/setter for types to add. If no arguments are passed, it simply
27 # returns the current array of accepted types.
28 def restricted_types(*types)
29 @_restricted_types ||= []
30 types.each do |type|
31 raise UnexpectedTypeException.new([Class],type.class) unless type.is_a? Class
32 @_restricted_types << type unless @_restricted_types.include? type
33 _subclasses.each do |subclass|
34 subclass.restricted_types type
35 end
36 end
37 @_restricted_types
38 end; alias :restricted_type :restricted_types
39
40 # The exception that is raised when an Unexpected Type is reached during validation
41 class UnexpectedTypeException < Exception
42 # Provide access to the types of objects expected and the class of the object received
43 attr_reader :expected, :received
44
45 def initialize expected_one_of, received
46 @expected = expected_one_of
47 @received = received
48 end
49
50 def to_s
51 %{Expected one of #{@expected.inspect} but received a(n) #{@received}}
52 end
53 end
54
55 protected
56
57 # a store of subclasses
58 def _subclasses
59 @_subclasses ||= []
60 end
61
62 end
63
64 # Provide a factory method. Takes any number of types to accept as arguments
65 # and returns a class that behaves as a type-enforced array.
66 def TypedArray *types_allowed
67 klass = Class.new( Array )
68 klass.class_exec(types_allowed) do |types_allowed|
69 extend TypedArray
70 restricted_types *types_allowed
71 restricted_types
72 end
73 klass.restricted_types
74 klass
75 end