fc8a3a10dc2425ddc15de2c138e41f44dc973658
[packages/precise/mcollective.git] / lib / mcollective / vendor / i18n / lib / i18n / backend / interpolation_compiler.rb
1 # The InterpolationCompiler module contains optimizations that can tremendously
2 # speed up the interpolation process on the Simple backend.
3 #
4 # It works by defining a pre-compiled method on stored translation Strings that
5 # already bring all the knowledge about contained interpolation variables etc.
6 # so that the actual recurring interpolation will be very fast.
7 #
8 # To enable pre-compiled interpolations you can simply include the
9 # InterpolationCompiler module to the Simple backend:
10 #
11 #   I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler)
12 #
13 # Note that InterpolationCompiler does not yield meaningful results and consequently
14 # should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
15 # (jRuby, Rubinius and 1.8.7).
16 module I18n
17   module Backend
18     module InterpolationCompiler
19       module Compiler
20         extend self
21
22         TOKENIZER                    = /(%%\{[^\}]+\}|%\{[^\}]+\})/
23         INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
24
25         def compile_if_an_interpolation(string)
26           if interpolated_str?(string)
27             string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
28               def i18n_interpolate(v = {})
29                 "#{compiled_interpolation_body(string)}"
30               end
31             RUBY_EVAL
32           end
33
34           string
35         end
36
37         def interpolated_str?(str)
38           str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
39         end
40
41         protected
42         # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
43         def tokenize(str)
44           str.split(TOKENIZER)
45         end
46
47         def compiled_interpolation_body(str)
48           tokenize(str).map do |token|
49             (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
50           end.join
51         end
52
53         def handle_interpolation_token(interpolation, matchdata)
54           escaped, pattern, key = matchdata.values_at(1, 2, 3)
55           escaped ? pattern : compile_interpolation_token(key.to_sym)
56         end
57
58         def compile_interpolation_token(key)
59           "\#{#{interpolate_or_raise_missing(key)}}"
60         end
61
62         def interpolate_or_raise_missing(key)
63           escaped_key = escape_key_sym(key)
64           RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
65         end
66
67         def interpolate_key(key)
68           [direct_key(key), nil_key(key), missing_key(key)].join('||')
69         end
70
71         def direct_key(key)
72           "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
73         end
74
75         def nil_key(key)
76           "(v.has_key?(#{key}) && '')"
77         end
78
79         def missing_key(key)
80           "raise(MissingInterpolationArgument.new(#{key}, self))"
81         end
82
83         def reserved_key(key)
84           "raise(ReservedInterpolationKey.new(#{key}, self))"
85         end
86
87         def escape_plain_str(str)
88           str.gsub(/"|\\|#/) {|x| "\\#{x}"}
89         end
90
91         def escape_key_sym(key)
92           # rely on Ruby to do all the hard work :)
93           key.to_sym.inspect
94         end
95       end
96
97       def interpolate(locale, string, values)
98         if string.respond_to?(:i18n_interpolate)
99           string.i18n_interpolate(values)
100         elsif values
101           super
102         else
103           string
104         end
105       end
106
107       def store_translations(locale, data, options = {})
108         compile_all_strings_in(data)
109         super
110       end
111
112       protected
113       def compile_all_strings_in(data)
114         data.each_value do |value|
115           Compiler.compile_if_an_interpolation(value)
116           compile_all_strings_in(value) if value.kind_of?(Hash)
117         end
118       end
119     end
120   end
121 end