tml 4.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +243 -0
- data/Rakefile +9 -0
- data/lib/tml.rb +56 -0
- data/lib/tml/api/client.rb +206 -0
- data/lib/tml/api/post_office.rb +71 -0
- data/lib/tml/application.rb +254 -0
- data/lib/tml/base.rb +116 -0
- data/lib/tml/cache.rb +143 -0
- data/lib/tml/cache_adapters/file.rb +89 -0
- data/lib/tml/cache_adapters/memcache.rb +104 -0
- data/lib/tml/cache_adapters/memory.rb +85 -0
- data/lib/tml/cache_adapters/redis.rb +108 -0
- data/lib/tml/config.rb +410 -0
- data/lib/tml/decorators/base.rb +52 -0
- data/lib/tml/decorators/default.rb +43 -0
- data/lib/tml/decorators/html.rb +102 -0
- data/lib/tml/exception.rb +35 -0
- data/lib/tml/ext/array.rb +86 -0
- data/lib/tml/ext/date.rb +99 -0
- data/lib/tml/ext/fixnum.rb +47 -0
- data/lib/tml/ext/hash.rb +99 -0
- data/lib/tml/ext/string.rb +56 -0
- data/lib/tml/ext/time.rb +89 -0
- data/lib/tml/generators/cache/base.rb +117 -0
- data/lib/tml/generators/cache/file.rb +159 -0
- data/lib/tml/language.rb +175 -0
- data/lib/tml/language_case.rb +105 -0
- data/lib/tml/language_case_rule.rb +76 -0
- data/lib/tml/language_context.rb +117 -0
- data/lib/tml/language_context_rule.rb +56 -0
- data/lib/tml/languages/en.json +1363 -0
- data/lib/tml/logger.rb +109 -0
- data/lib/tml/rules_engine/evaluator.rb +162 -0
- data/lib/tml/rules_engine/parser.rb +65 -0
- data/lib/tml/session.rb +199 -0
- data/lib/tml/source.rb +106 -0
- data/lib/tml/tokenizers/data.rb +96 -0
- data/lib/tml/tokenizers/decoration.rb +204 -0
- data/lib/tml/tokenizers/dom.rb +346 -0
- data/lib/tml/tokens/data.rb +403 -0
- data/lib/tml/tokens/method.rb +61 -0
- data/lib/tml/tokens/transform.rb +223 -0
- data/lib/tml/translation.rb +67 -0
- data/lib/tml/translation_key.rb +178 -0
- data/lib/tml/translator.rb +47 -0
- data/lib/tml/utils.rb +130 -0
- data/lib/tml/version.rb +34 -0
- metadata +121 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2015 Translation Exchange, Inc
|
4
|
+
#
|
5
|
+
# _______ _ _ _ ______ _
|
6
|
+
# |__ __| | | | | (_) | ____| | |
|
7
|
+
# | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
|
8
|
+
# | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
|
9
|
+
# | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
|
10
|
+
# |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
|
11
|
+
# __/ |
|
12
|
+
# |___/
|
13
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
14
|
+
# a copy of this software and associated documentation files (the
|
15
|
+
# "Software"), to deal in the Software without restriction, including
|
16
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
17
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
18
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
19
|
+
# the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be
|
22
|
+
# included in all copies or substantial portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
25
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
26
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
27
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
28
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
29
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
30
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
|
+
#++
|
32
|
+
|
33
|
+
class Tml::Api::PostOffice < Tml::Api::Client
|
34
|
+
POST_OFFICE_HOST = 'https://postoffice.translationexchange.com'
|
35
|
+
POST_OFFICE_API_VERSION = 'v1'
|
36
|
+
|
37
|
+
attributes :application
|
38
|
+
|
39
|
+
def templates
|
40
|
+
api('templates', {
|
41
|
+
:access_token => access_token
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
def channels
|
46
|
+
api('channels', {
|
47
|
+
:access_token => access_token
|
48
|
+
})
|
49
|
+
end
|
50
|
+
|
51
|
+
def deliver(template, to, tokens = {}, options = {})
|
52
|
+
api("templates/#{template}/deliver", {
|
53
|
+
:access_token => access_token,
|
54
|
+
:tokens => tokens,
|
55
|
+
:to => to,
|
56
|
+
:via => options[:via],
|
57
|
+
:from => options[:from],
|
58
|
+
:locale => options[:locale],
|
59
|
+
:realtime => options[:realtime]
|
60
|
+
}, options.merge(:method => :post))
|
61
|
+
end
|
62
|
+
|
63
|
+
def host
|
64
|
+
application.tools['postoffice'] || POST_OFFICE_HOST
|
65
|
+
end
|
66
|
+
|
67
|
+
def prepare_api_path(path)
|
68
|
+
"/api/#{POST_OFFICE_API_VERSION}#{path.first == '/' ? '' : '/'}#{path}"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2015 Translation Exchange, Inc
|
4
|
+
#
|
5
|
+
# _______ _ _ _ ______ _
|
6
|
+
# |__ __| | | | | (_) | ____| | |
|
7
|
+
# | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
|
8
|
+
# | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
|
9
|
+
# | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
|
10
|
+
# |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
|
11
|
+
# __/ |
|
12
|
+
# |___/
|
13
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
14
|
+
# a copy of this software and associated documentation files (the
|
15
|
+
# "Software"), to deal in the Software without restriction, including
|
16
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
17
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
18
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
19
|
+
# the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be
|
22
|
+
# included in all copies or substantial portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
25
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
26
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
27
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
28
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
29
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
30
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
|
+
#++
|
32
|
+
|
33
|
+
require 'faraday'
|
34
|
+
|
35
|
+
class Tml::Application < Tml::Base
|
36
|
+
attributes :host, :id, :key, :secret, :access_token, :name, :description, :threshold, :default_locale, :default_level, :tools
|
37
|
+
has_many :features, :languages, :featured_locales, :sources, :components, :tokens, :css, :shortcuts, :translations
|
38
|
+
|
39
|
+
def self.cache_key
|
40
|
+
'application'
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.translations_cache_key(locale)
|
44
|
+
"#{locale}/translations"
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch
|
48
|
+
update_attributes(api_client.get('applications/current', {:definition => true}, {:cache_key => self.class.cache_key}))
|
49
|
+
rescue Tml::Exception => ex
|
50
|
+
Tml.logger.error("Failed to load application: #{ex}")
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_attributes(attrs)
|
55
|
+
super
|
56
|
+
|
57
|
+
self.attributes[:languages] = []
|
58
|
+
if hash_value(attrs, :languages)
|
59
|
+
self.attributes[:languages] = hash_value(attrs, :languages).collect{ |l| Tml::Language.new(l.merge(:application => self)) }
|
60
|
+
end
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def language(locale = nil)
|
66
|
+
locale = nil if locale.strip == ''
|
67
|
+
locale ||= default_locale || Tml.config.default_locale
|
68
|
+
@languages_by_locale ||= {}
|
69
|
+
@languages_by_locale[locale] ||= api_client.get(
|
70
|
+
"languages/#{locale}",
|
71
|
+
{:definition => true},
|
72
|
+
{
|
73
|
+
:class => Tml::Language,
|
74
|
+
:attributes => {:locale => locale, :application => self},
|
75
|
+
:cache_key => Tml::Language.cache_key(locale)
|
76
|
+
}
|
77
|
+
)
|
78
|
+
rescue Tml::Exception => e
|
79
|
+
Tml.logger.error(e)
|
80
|
+
Tml.logger.error(e.backtrace)
|
81
|
+
@languages_by_locale[locale] = Tml.config.default_language
|
82
|
+
end
|
83
|
+
|
84
|
+
# Mostly used for testing
|
85
|
+
def add_language(new_language)
|
86
|
+
@languages_by_locale ||= {}
|
87
|
+
return @languages_by_locale[new_language.locale] if @languages_by_locale[new_language.locale]
|
88
|
+
new_language.application = self
|
89
|
+
self.languages << new_language
|
90
|
+
@languages_by_locale[new_language.locale] = new_language
|
91
|
+
new_language
|
92
|
+
end
|
93
|
+
|
94
|
+
def locales
|
95
|
+
@locales ||= languages.collect{|lang| lang.locale}
|
96
|
+
end
|
97
|
+
|
98
|
+
def tools
|
99
|
+
@attributes[:tools] || {}
|
100
|
+
end
|
101
|
+
|
102
|
+
def url_for(path)
|
103
|
+
"#{tools['assets']}#{path}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def source(source, locale)
|
107
|
+
self.sources ||= {}
|
108
|
+
self.sources[source] ||= Tml::Source.new(
|
109
|
+
:application => self,
|
110
|
+
:source => source
|
111
|
+
).fetch_translations(locale)
|
112
|
+
end
|
113
|
+
|
114
|
+
def component(key, register = true)
|
115
|
+
key = key.key if key.is_a?(Tml::Component)
|
116
|
+
|
117
|
+
return self.components[key] if self.components[key]
|
118
|
+
return nil unless register
|
119
|
+
|
120
|
+
self.components[key] ||= api_client.post('components/register', {:component => key}, {:class => Tml::Component, :attributes => {:application => self}})
|
121
|
+
end
|
122
|
+
|
123
|
+
def register_missing_key(source_key, tkey)
|
124
|
+
return if Tml.cache.read_only? and not Tml.session.inline_mode?
|
125
|
+
|
126
|
+
@missing_keys_by_sources ||= {}
|
127
|
+
@missing_keys_by_sources[source_key] ||= {}
|
128
|
+
@missing_keys_by_sources[source_key][tkey.key] ||= tkey
|
129
|
+
submit_missing_keys if Tml.config.submit_missing_keys_realtime
|
130
|
+
end
|
131
|
+
|
132
|
+
def register_keys(keys)
|
133
|
+
params = []
|
134
|
+
keys.each do |source_key, keys|
|
135
|
+
next unless keys.values.any?
|
136
|
+
source = Tml::Source.new(:source => source_key, :application => self)
|
137
|
+
params << {:source => source_key, :keys => keys.values.collect{|tkey| tkey.to_hash(:label, :description, :locale, :level)}}
|
138
|
+
source.reset_cache
|
139
|
+
end
|
140
|
+
|
141
|
+
api_client.post('sources/register_keys', {:source_keys => params.to_json})
|
142
|
+
rescue Tml::Exception => e
|
143
|
+
Tml.logger.error('Failed to register missing translation keys...')
|
144
|
+
Tml.logger.error(e)
|
145
|
+
Tml.logger.error(e.backtrace)
|
146
|
+
end
|
147
|
+
|
148
|
+
def submit_missing_keys
|
149
|
+
return if @missing_keys_by_sources.nil? or @missing_keys_by_sources.empty?
|
150
|
+
register_keys(@missing_keys_by_sources)
|
151
|
+
@missing_keys_by_sources = nil
|
152
|
+
end
|
153
|
+
|
154
|
+
def featured_languages
|
155
|
+
@featured_languages ||= begin
|
156
|
+
locales = api_client.get('applications/current/featured_locales', {}, {:cache_key => 'featured_locales'})
|
157
|
+
(locales.nil? or locales.empty?) ? [] : languages.select{|l| locales.include?(l.locale)}
|
158
|
+
end
|
159
|
+
rescue
|
160
|
+
[]
|
161
|
+
end
|
162
|
+
|
163
|
+
def translators
|
164
|
+
@translators ||= api_client.get('applications/current/translators', {}, {:class => Tml::Translator, :attributes => {:application => self}})
|
165
|
+
rescue
|
166
|
+
[]
|
167
|
+
end
|
168
|
+
|
169
|
+
def reset_translation_cache
|
170
|
+
self.sources = {}
|
171
|
+
self.translations = {}
|
172
|
+
@languages_by_locale = nil
|
173
|
+
@missing_keys_by_sources = nil
|
174
|
+
end
|
175
|
+
|
176
|
+
def fetch_translations(locale)
|
177
|
+
self.translations ||= {}
|
178
|
+
self.translations[locale] ||= begin
|
179
|
+
results = Tml.cache.fetch(Tml::Application.translations_cache_key(locale)) do
|
180
|
+
data = {}
|
181
|
+
unless Tml.cache.read_only?
|
182
|
+
api_client.paginate('applications/current/translations', :per_page => 1000) do |translations|
|
183
|
+
data.merge!(translations)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
data
|
187
|
+
end
|
188
|
+
|
189
|
+
translations_by_key = {}
|
190
|
+
results.each do |key, data|
|
191
|
+
translations_data = data.is_a?(Hash) ? data['translations'] : data
|
192
|
+
translations_by_key[key] = translations_data.collect do |t|
|
193
|
+
Tml::Translation.new(
|
194
|
+
:locale => t['locale'] || locale,
|
195
|
+
:label => t['label'],
|
196
|
+
:context => t['context']
|
197
|
+
)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
translations_by_key
|
201
|
+
end
|
202
|
+
rescue Tml::Exception => ex
|
203
|
+
{}
|
204
|
+
end
|
205
|
+
|
206
|
+
def cache_translations(locale, key, new_translations)
|
207
|
+
self.translations ||= {}
|
208
|
+
self.translations[locale] ||= {}
|
209
|
+
self.translations[locale][key] = new_translations.collect do |t|
|
210
|
+
Tml::Translation.new(
|
211
|
+
:locale => t['locale'] || locale,
|
212
|
+
:label => t['label'],
|
213
|
+
:context => t['context']
|
214
|
+
)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def cached_translations(locale, key)
|
219
|
+
return unless self.translations and self.translations[locale]
|
220
|
+
self.translations[locale][key]
|
221
|
+
end
|
222
|
+
|
223
|
+
def debug_translations
|
224
|
+
return 'no translations' unless self.translations
|
225
|
+
self.translations.each do |locale, keys|
|
226
|
+
pp [locale, keys.collect{|key, translations|
|
227
|
+
[key, translations.collect{|t|
|
228
|
+
[t.label, t.context]
|
229
|
+
}]
|
230
|
+
}]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def default_decoration_token(token)
|
235
|
+
hash_value(tokens, "decoration.#{token.to_s}")
|
236
|
+
end
|
237
|
+
|
238
|
+
def default_data_token(token)
|
239
|
+
hash_value(tokens, "data.#{token.to_s}")
|
240
|
+
end
|
241
|
+
|
242
|
+
def feature_enabled?(key)
|
243
|
+
hash_value(features, key.to_s)
|
244
|
+
end
|
245
|
+
|
246
|
+
def api_client
|
247
|
+
@api_client ||= Tml::Api::Client.new(:application => self)
|
248
|
+
end
|
249
|
+
|
250
|
+
def postoffice
|
251
|
+
@postoffice ||= Tml::Api::PostOffice.new(:application => self)
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
data/lib/tml/base.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2015 Translation Exchange, Inc
|
4
|
+
#
|
5
|
+
# _______ _ _ _ ______ _
|
6
|
+
# |__ __| | | | | (_) | ____| | |
|
7
|
+
# | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
|
8
|
+
# | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
|
9
|
+
# | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
|
10
|
+
# |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
|
11
|
+
# __/ |
|
12
|
+
# |___/
|
13
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
14
|
+
# a copy of this software and associated documentation files (the
|
15
|
+
# "Software"), to deal in the Software without restriction, including
|
16
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
17
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
18
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
19
|
+
# the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be
|
22
|
+
# included in all copies or substantial portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
25
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
26
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
27
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
28
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
29
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
30
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
|
+
#++
|
32
|
+
|
33
|
+
class Tml::Base
|
34
|
+
attr_reader :attributes
|
35
|
+
|
36
|
+
def initialize(attrs = {})
|
37
|
+
@attributes = {}
|
38
|
+
update_attributes(attrs)
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_attributes(attrs = {})
|
42
|
+
attrs.each do |key, value|
|
43
|
+
#pp [self.class.name, key, self.class.attributes, self.class.attributes.include?(key.to_sym)]
|
44
|
+
next unless self.class.attributes.include?(key.to_sym)
|
45
|
+
@attributes[key.to_sym] = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.attributes(*attrs)
|
50
|
+
@attribute_names ||= []
|
51
|
+
@attribute_names += attrs.collect{|a| a.to_sym} unless attrs.nil?
|
52
|
+
@attribute_names
|
53
|
+
end
|
54
|
+
def self.belongs_to(*attrs) self.attributes(*attrs); end
|
55
|
+
def self.has_many(*attrs) self.attributes(*attrs); end
|
56
|
+
|
57
|
+
def method_missing(meth, *args, &block)
|
58
|
+
method_name = meth.to_s
|
59
|
+
method_suffix = method_name[-1, 1]
|
60
|
+
method_key = method_name.to_sym
|
61
|
+
if %w(= ?).include?(method_suffix)
|
62
|
+
method_key = method_name[0..-2].to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
if self.class.attributes.index(method_key)
|
66
|
+
if method_suffix == '='
|
67
|
+
attributes[method_key] = args.first
|
68
|
+
return attributes[method_key]
|
69
|
+
end
|
70
|
+
return attributes[method_key]
|
71
|
+
end
|
72
|
+
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.hash_value(hash, key, opts = {})
|
77
|
+
return nil unless hash.is_a?(Hash)
|
78
|
+
return hash[key.to_s] || hash[key.to_sym] if opts[:whole]
|
79
|
+
|
80
|
+
value = hash
|
81
|
+
key.to_s.split('.').each do |part|
|
82
|
+
return nil unless value.is_a?(Hash)
|
83
|
+
value = value[part.to_sym] || value[part.to_s]
|
84
|
+
end
|
85
|
+
value
|
86
|
+
end
|
87
|
+
|
88
|
+
def hash_value(hash, key, opts = {})
|
89
|
+
self.class.hash_value(hash, key, opts)
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_hash(*attrs)
|
93
|
+
if attrs.nil? or attrs.empty?
|
94
|
+
# default hashing only includes basic types
|
95
|
+
keys = []
|
96
|
+
self.class.attributes.each do |key|
|
97
|
+
value = attributes[key]
|
98
|
+
next if value.kind_of?(Tml::Base) or value.kind_of?(Hash) or value.kind_of?(Array)
|
99
|
+
keys << key
|
100
|
+
end
|
101
|
+
else
|
102
|
+
keys = attrs
|
103
|
+
end
|
104
|
+
|
105
|
+
hash = {}
|
106
|
+
keys.each do |key|
|
107
|
+
hash[key] = attributes[key]
|
108
|
+
end
|
109
|
+
|
110
|
+
proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&proc); nil) : v.nil? }
|
111
|
+
hash.delete_if(&proc)
|
112
|
+
|
113
|
+
hash
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
data/lib/tml/cache.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2015 Translation Exchange, Inc
|
4
|
+
#
|
5
|
+
# _______ _ _ _ ______ _
|
6
|
+
# |__ __| | | | | (_) | ____| | |
|
7
|
+
# | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
|
8
|
+
# | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
|
9
|
+
# | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
|
10
|
+
# |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
|
11
|
+
# __/ |
|
12
|
+
# |___/
|
13
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
14
|
+
# a copy of this software and associated documentation files (the
|
15
|
+
# "Software"), to deal in the Software without restriction, including
|
16
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
17
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
18
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
19
|
+
# the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be
|
22
|
+
# included in all copies or substantial portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
25
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
26
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
27
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
28
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
29
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
30
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
|
+
#++
|
32
|
+
|
33
|
+
module Tml
|
34
|
+
|
35
|
+
CACHE_VERSION_KEY = '__tml_version__'
|
36
|
+
|
37
|
+
def self.memory
|
38
|
+
@memory ||= Tml::CacheAdapters::Memory.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.cache
|
42
|
+
@cache ||= begin
|
43
|
+
if Tml.config.cache_enabled?
|
44
|
+
klass = Tml::CacheAdapters.const_get(Tml.config.cache[:adapter].camelcase)
|
45
|
+
klass.new
|
46
|
+
else
|
47
|
+
# blank implementation
|
48
|
+
Tml::Cache.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Cache
|
54
|
+
|
55
|
+
def version
|
56
|
+
Tml.config.cache[:version] ||= 1
|
57
|
+
|
58
|
+
@version ||= begin
|
59
|
+
v = fetch(CACHE_VERSION_KEY) do
|
60
|
+
{'version' => Tml.config.cache[:version]}
|
61
|
+
end
|
62
|
+
v['version']
|
63
|
+
end
|
64
|
+
|
65
|
+
@version ||= Tml.config.cache[:version]
|
66
|
+
|
67
|
+
if Tml.config.cache[:version] > @version
|
68
|
+
update_version(Tml.config.cache[:version])
|
69
|
+
@version = Tml.config.cache[:version]
|
70
|
+
end
|
71
|
+
|
72
|
+
@version
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_version(new_version)
|
76
|
+
store(CACHE_VERSION_KEY, {'version' => new_version}, :ttl => 0)
|
77
|
+
end
|
78
|
+
|
79
|
+
def upgrade_version
|
80
|
+
update_version(version + 1)
|
81
|
+
@version = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def reset_version
|
85
|
+
@version = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def enabled?
|
89
|
+
Tml.config.cache[:enabled]
|
90
|
+
end
|
91
|
+
|
92
|
+
def cached_by_source?
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_only?
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def segmented?
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def cache_name
|
105
|
+
self.class.name.split('::').last
|
106
|
+
end
|
107
|
+
|
108
|
+
def info(msg)
|
109
|
+
Tml.logger.info("#{cache_name} - #{msg}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def warn(msg)
|
113
|
+
Tml.logger.warn("#{cache_name} - #{msg}")
|
114
|
+
end
|
115
|
+
|
116
|
+
def versioned_key(key, opts = {})
|
117
|
+
return key if CACHE_VERSION_KEY == key
|
118
|
+
"tml_rc_v#{version}_#{key}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def fetch(key, opts = {})
|
122
|
+
return nil unless block_given?
|
123
|
+
yield
|
124
|
+
end
|
125
|
+
|
126
|
+
def store(key, data, opts = {})
|
127
|
+
# do nothing
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete(key, opts = {})
|
131
|
+
# do nothing
|
132
|
+
end
|
133
|
+
|
134
|
+
def exist?(key, opts = {})
|
135
|
+
false
|
136
|
+
end
|
137
|
+
|
138
|
+
def clear(opts = {})
|
139
|
+
# do nothing
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|