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,89 @@
|
|
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::CacheAdapters::File < Tml::Cache
|
34
|
+
|
35
|
+
def self.cache_path
|
36
|
+
"#{Tml.config.cache[:path]}/#{Tml.config.cache[:version]}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.file_path(key)
|
40
|
+
File.join(cache_path, "#{key}.json")
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_name
|
44
|
+
'file'
|
45
|
+
end
|
46
|
+
|
47
|
+
def segmented?
|
48
|
+
return true if Tml.config.cache[:segmented].nil?
|
49
|
+
Tml.config.cache[:segmented]
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch(key, opts = {})
|
53
|
+
info("Fetching key: #{key}")
|
54
|
+
|
55
|
+
path = self.class.file_path(key)
|
56
|
+
|
57
|
+
if File.exists?(path)
|
58
|
+
info("Cache hit: #{key}")
|
59
|
+
return JSON.parse(File.read(path))
|
60
|
+
end
|
61
|
+
|
62
|
+
info("Cache miss: #{key}")
|
63
|
+
|
64
|
+
return nil unless block_given?
|
65
|
+
|
66
|
+
yield
|
67
|
+
end
|
68
|
+
|
69
|
+
def store(key, data, opts = {})
|
70
|
+
warn('This is a readonly cache')
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete(key, opts = {})
|
74
|
+
warn('This is a readonly cache')
|
75
|
+
end
|
76
|
+
|
77
|
+
def exist?(key, opts = {})
|
78
|
+
File.exists?(self.class.file_path(key))
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear(opts = {})
|
82
|
+
warn('This is a readonly cache')
|
83
|
+
end
|
84
|
+
|
85
|
+
def read_only?
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,104 @@
|
|
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 'dalli' if defined?(Dalli)
|
34
|
+
|
35
|
+
class Tml::CacheAdapters::Memcache < Tml::Cache
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
options = { :namespace => Tml.config.cache[:namespace] || 'tml', :compress => Tml.config.cache[:compress].nil? ? true : Tml.config.cache[:compress]}
|
39
|
+
@cache = Dalli::Client.new(Tml.config.cache[:host], options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def cache_name
|
43
|
+
'memcache'
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_only?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch(key, opts = {})
|
51
|
+
data = @cache.get(versioned_key(key, opts))
|
52
|
+
if data
|
53
|
+
info("Cache hit: #{key}")
|
54
|
+
return data
|
55
|
+
end
|
56
|
+
|
57
|
+
info("Cache miss: #{key}")
|
58
|
+
|
59
|
+
return nil unless block_given?
|
60
|
+
|
61
|
+
data = yield
|
62
|
+
|
63
|
+
store(key, data)
|
64
|
+
|
65
|
+
data
|
66
|
+
rescue Exception => ex
|
67
|
+
warn("Failed to retrieve data: #{key}")
|
68
|
+
return nil unless block_given?
|
69
|
+
yield
|
70
|
+
end
|
71
|
+
|
72
|
+
def store(key, data, opts = {})
|
73
|
+
info("Cache store: #{key}")
|
74
|
+
ttl = opts[:ttl] || Tml.config.cache[:timeout]
|
75
|
+
@cache.set(versioned_key(key, opts), data, ttl)
|
76
|
+
data
|
77
|
+
rescue Exception => ex
|
78
|
+
warn("Failed to store data: #{key}")
|
79
|
+
data
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete(key, opts = {})
|
83
|
+
info("Cache delete: #{key}")
|
84
|
+
@cache.delete(versioned_key(key, opts))
|
85
|
+
key
|
86
|
+
rescue Exception => ex
|
87
|
+
warn("Failed to delete data: #{key}")
|
88
|
+
key
|
89
|
+
end
|
90
|
+
|
91
|
+
def exist?(key, opts = {})
|
92
|
+
data = @cache.get(versioned_key(key, opts))
|
93
|
+
not data.nil?
|
94
|
+
rescue Exception => ex
|
95
|
+
warn("Failed to check if key exists: #{key}")
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
def clear(opts = {})
|
100
|
+
info("Cache clear")
|
101
|
+
rescue Exception => ex
|
102
|
+
warn("Failed to clear cache: #{key}")
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,85 @@
|
|
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::CacheAdapters::Memory < Tml::Cache
|
34
|
+
|
35
|
+
def self.cache
|
36
|
+
@cache ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.clear_cache
|
40
|
+
@cache = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_name
|
44
|
+
'memory'
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch(key, opts = {})
|
48
|
+
return yield if Tml.session.inline_mode?
|
49
|
+
return yield unless Tml.cache.read_only?
|
50
|
+
|
51
|
+
data = self.class.cache[key]
|
52
|
+
|
53
|
+
if data
|
54
|
+
info("Cache hit: #{key}")
|
55
|
+
return data
|
56
|
+
end
|
57
|
+
|
58
|
+
info("Cache miss: #{key}")
|
59
|
+
|
60
|
+
return nil unless block_given?
|
61
|
+
|
62
|
+
data = yield
|
63
|
+
|
64
|
+
store(key, data)
|
65
|
+
|
66
|
+
data
|
67
|
+
end
|
68
|
+
|
69
|
+
def store(key, data, opts = {})
|
70
|
+
self.class.cache[key] = data
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete(key, opts = {})
|
74
|
+
self.class.cache[key] = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def exist?(key, opts = {})
|
78
|
+
not self.class.cache[key].nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear(opts = {})
|
82
|
+
self.class.clear_cache
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,108 @@
|
|
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
|
+
|
34
|
+
require 'redis' if defined?(::Redis)
|
35
|
+
|
36
|
+
class Tml::CacheAdapters::Redis < Tml::Cache
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
cache_host, cache_port = Tml.config.cache[:host].split(':') if Tml.config.cache[:host]
|
40
|
+
cache_host ||= 'localhost'
|
41
|
+
cache_port ||= 6379
|
42
|
+
|
43
|
+
@cache = ::Redis.new(host: cache_host, port: cache_port)
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache_name
|
47
|
+
"redis"
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_only?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch(key, opts = {})
|
55
|
+
data = @cache.get(versioned_key(key, opts))
|
56
|
+
if data
|
57
|
+
info("Cache hit: #{key}")
|
58
|
+
return data
|
59
|
+
end
|
60
|
+
|
61
|
+
info("Cache miss: #{key}")
|
62
|
+
|
63
|
+
return nil unless block_given?
|
64
|
+
|
65
|
+
data = yield
|
66
|
+
|
67
|
+
store(key, data)
|
68
|
+
|
69
|
+
data
|
70
|
+
rescue Exception => ex
|
71
|
+
warn("Failed to retrieve data: #{ex.message}")
|
72
|
+
return nil unless block_given?
|
73
|
+
yield
|
74
|
+
end
|
75
|
+
|
76
|
+
def store(key, data, opts = {})
|
77
|
+
info("Cache store: #{key}")
|
78
|
+
ttl = opts[:ttl] || Tml.config.cache[:timeout]
|
79
|
+
versioned_key = versioned_key(key, opts)
|
80
|
+
|
81
|
+
@cache.set(versioned_key, data)
|
82
|
+
@cache.expire(versioned_key, ttl) if ttl and ttl > 0
|
83
|
+
rescue Exception => ex
|
84
|
+
warn("Failed to store data: #{ex.message}")
|
85
|
+
data
|
86
|
+
end
|
87
|
+
|
88
|
+
def delete(key, opts = {})
|
89
|
+
info("Cache delete: #{key}")
|
90
|
+
@cache.del(versioned_key(key, opts))
|
91
|
+
rescue Exception => ex
|
92
|
+
warn("Failed to delete data: #{ex.message}")
|
93
|
+
key
|
94
|
+
end
|
95
|
+
|
96
|
+
def exist?(key, opts = {})
|
97
|
+
data = @cache.exist(versioned_key(key, opts))
|
98
|
+
not data.nil?
|
99
|
+
rescue Exception => ex
|
100
|
+
warn("Failed to check if key exists: #{ex.message}")
|
101
|
+
false
|
102
|
+
end
|
103
|
+
|
104
|
+
def clear(opts = {})
|
105
|
+
info('Cache clear has no effect')
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
data/lib/tml/config.rb
ADDED
@@ -0,0 +1,410 @@
|
|
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
|
+
class << self
|
36
|
+
attr_accessor :config
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initializes default config
|
40
|
+
|
41
|
+
def self.config
|
42
|
+
@config ||= Tml::Config.new
|
43
|
+
end
|
44
|
+
|
45
|
+
# Allows you to configure Tml
|
46
|
+
#
|
47
|
+
# Tml.configure do |config|
|
48
|
+
# config.application = {:key => "", :secret => ""}
|
49
|
+
#
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
|
53
|
+
def self.configure
|
54
|
+
yield(self.config)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Allows you to create a block to perform something on adjusted config settings
|
58
|
+
# Once the block exists, the config will be reset back to what it was before:
|
59
|
+
#
|
60
|
+
# Tml.with_config_settings do |config|
|
61
|
+
# config.format = :text
|
62
|
+
#
|
63
|
+
# Do something....
|
64
|
+
#
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
|
68
|
+
def self.with_config_settings
|
69
|
+
old_config = @config.dup
|
70
|
+
yield(@config)
|
71
|
+
@config = old_config
|
72
|
+
end
|
73
|
+
|
74
|
+
# Acts as a global singleton that holds all Tml configuration
|
75
|
+
# The class can be extended with a different implementation, as long as the interface is supported
|
76
|
+
class Config
|
77
|
+
# Configuration Attributes
|
78
|
+
attr_accessor :enabled, :default_locale, :default_level, :format, :application, :context_rules, :logger, :cache, :default_tokens, :localization
|
79
|
+
|
80
|
+
# Used by Rails and Sinatra extensions
|
81
|
+
attr_accessor :current_locale_method, :current_user_method, :translator_options
|
82
|
+
|
83
|
+
# Used for IRB only
|
84
|
+
attr_accessor :submit_missing_keys_realtime
|
85
|
+
|
86
|
+
def initialize
|
87
|
+
@enabled = true
|
88
|
+
@default_locale = 'en'
|
89
|
+
@default_level = 0
|
90
|
+
@format = :html
|
91
|
+
|
92
|
+
# if running from IRB, make it default to TRUE
|
93
|
+
@submit_missing_keys_realtime = (%w(irb pry).include?($0 || ''))
|
94
|
+
|
95
|
+
@current_locale_method = :current_locale
|
96
|
+
@current_user_method = :current_user
|
97
|
+
|
98
|
+
@application = nil
|
99
|
+
|
100
|
+
@translator_options = {
|
101
|
+
debug: false,
|
102
|
+
debug_format_html: "<span style='font-size:20px;color:red;'>{</span> {$0} <span style='font-size:20px;color:red;'>}</span>",
|
103
|
+
debug_format: '{{{{$0}}}}',
|
104
|
+
split_sentences: false,
|
105
|
+
nodes: {
|
106
|
+
ignored: [],
|
107
|
+
scripts: %w(style script),
|
108
|
+
inline: %w(a span i b img strong s em u sub sup),
|
109
|
+
short: %w(i b),
|
110
|
+
splitters: %w(br hr)
|
111
|
+
},
|
112
|
+
attributes: {
|
113
|
+
labels: %w(title alt)
|
114
|
+
},
|
115
|
+
name_mapping: {
|
116
|
+
b: 'bold',
|
117
|
+
i: 'italic',
|
118
|
+
a: 'link',
|
119
|
+
img: 'picture'
|
120
|
+
},
|
121
|
+
data_tokens: {
|
122
|
+
special: false,
|
123
|
+
numeric: false,
|
124
|
+
numeric_name: 'num'
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
@context_rules = {
|
129
|
+
:number => {
|
130
|
+
:variables => {
|
131
|
+
}
|
132
|
+
},
|
133
|
+
:gender => {
|
134
|
+
:variables => {
|
135
|
+
'@gender' => 'gender',
|
136
|
+
}
|
137
|
+
},
|
138
|
+
:genders => {
|
139
|
+
:variables => {
|
140
|
+
'@genders' => lambda{|list| list.collect do |u|
|
141
|
+
u.is_a?(Hash) ? (u['gender'] || u[:gender]) : u.gender
|
142
|
+
end
|
143
|
+
},
|
144
|
+
'@size' => lambda{ |list| list.size }
|
145
|
+
}
|
146
|
+
},
|
147
|
+
:date => {
|
148
|
+
:variables => {
|
149
|
+
}
|
150
|
+
},
|
151
|
+
:time => {
|
152
|
+
:variables => {
|
153
|
+
}
|
154
|
+
},
|
155
|
+
:list => {
|
156
|
+
:variables => {
|
157
|
+
'@count' => lambda{|list| list.size}
|
158
|
+
}
|
159
|
+
},
|
160
|
+
}
|
161
|
+
|
162
|
+
@logger = {
|
163
|
+
:enabled => false,
|
164
|
+
:path => './log/tml.log',
|
165
|
+
:level => 'debug'
|
166
|
+
}
|
167
|
+
|
168
|
+
@cache = {
|
169
|
+
:enabled => false,
|
170
|
+
:host => 'localhost:11211',
|
171
|
+
:adapter => 'memcache',
|
172
|
+
:version => 1,
|
173
|
+
:timeout => 3600
|
174
|
+
}
|
175
|
+
|
176
|
+
@default_tokens = {
|
177
|
+
:html => {
|
178
|
+
:data => {
|
179
|
+
:ndash => "–", # –
|
180
|
+
:mdash => "—", # —
|
181
|
+
:iexcl => "¡", # ¡
|
182
|
+
:iquest => "¿", # ¿
|
183
|
+
:quot => """, # "
|
184
|
+
:ldquo => "“", # “
|
185
|
+
:rdquo => "”", # ”
|
186
|
+
:lsquo => "‘", # ‘
|
187
|
+
:rsquo => "’", # ’
|
188
|
+
:laquo => "«", # «
|
189
|
+
:raquo => "»", # »
|
190
|
+
:nbsp => " ", # space
|
191
|
+
:lsaquo => "‹", # ‹
|
192
|
+
:rsaquo => "›", # ›
|
193
|
+
:br => "<br/>", # line break
|
194
|
+
:lbrace => "{",
|
195
|
+
:rbrace => "}",
|
196
|
+
:trade => "™", # TM
|
197
|
+
},
|
198
|
+
:decoration => {
|
199
|
+
:strong => "<strong>{$0}</strong>",
|
200
|
+
:bold => "<strong>{$0}</strong>",
|
201
|
+
:b => "<strong>{$0}</strong>",
|
202
|
+
:em => "<em>{$0}</em>",
|
203
|
+
:italic => "<i>{$0}</i>",
|
204
|
+
:i => "<i>{$0}</i>",
|
205
|
+
:link => "<a href='{$href}'>{$0}</a>",
|
206
|
+
:br => "<br>{$0}",
|
207
|
+
:strike => "<strike>{$0}</strike>",
|
208
|
+
:div => "<div id='{$id}' class='{$class}' style='{$style}'>{$0}</div>",
|
209
|
+
:span => "<span id='{$id}' class='{$class}' style='{$style}'>{$0}</span>",
|
210
|
+
:h1 => "<h1>{$0}</h1>",
|
211
|
+
:h2 => "<h2>{$0}</h2>",
|
212
|
+
:h3 => "<h3>{$0}</h3>",
|
213
|
+
}
|
214
|
+
},
|
215
|
+
:text => {
|
216
|
+
:data => {
|
217
|
+
:ndash => "–",
|
218
|
+
:mdash => "-",
|
219
|
+
:iexcl => "¡",
|
220
|
+
:iquest => "¿",
|
221
|
+
:quot => '"',
|
222
|
+
:ldquo => "“",
|
223
|
+
:rdquo => "”",
|
224
|
+
:lsquo => "‘",
|
225
|
+
:rsquo => "’",
|
226
|
+
:laquo => "«",
|
227
|
+
:raquo => "»",
|
228
|
+
:nbsp => " ",
|
229
|
+
:lsaquo => "‹",
|
230
|
+
:rsaquo => "›",
|
231
|
+
:br => "\n",
|
232
|
+
:lbrace => "{",
|
233
|
+
:rbrace => "}",
|
234
|
+
:trade => "™",
|
235
|
+
},
|
236
|
+
:decoration => {
|
237
|
+
:strong => "{$0}",
|
238
|
+
:bold => "{$0}",
|
239
|
+
:b => "{$0}",
|
240
|
+
:em => "{$0}",
|
241
|
+
:italic => "{$0}",
|
242
|
+
:i => "{$0}",
|
243
|
+
:link => "{$0}:{$1}",
|
244
|
+
:br => "\n{$0}",
|
245
|
+
:strike => "{$0}",
|
246
|
+
:div => "{$0}",
|
247
|
+
:span => "{$0}",
|
248
|
+
:h1 => "{$0}",
|
249
|
+
:h2 => "{$0}",
|
250
|
+
:h3 => "{$0}",
|
251
|
+
}
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
@localization = {
|
256
|
+
:default_day_names => ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
257
|
+
:default_abbr_day_names => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
258
|
+
:default_month_names => ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
259
|
+
:default_abbr_month_names => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
260
|
+
:custom_date_formats => {
|
261
|
+
:default => '%m/%d/%Y', # 07/4/2008
|
262
|
+
:short_numeric => '%m/%d', # 07/4
|
263
|
+
:short_numeric_year => '%m/%d/%y', # 07/4/08
|
264
|
+
:long_numeric => '%m/%d/%Y', # 07/4/2008
|
265
|
+
:verbose => '%A, %B %d, %Y', # Friday, July 4, 2008
|
266
|
+
:monthname => '%B %d', # July 4
|
267
|
+
:monthname_year => '%B %d, %Y', # July 4, 2008
|
268
|
+
:monthname_abbr => '%b %d', # Jul 4
|
269
|
+
:monthname_abbr_year => '%b %d, %Y', # Jul 4, 2008
|
270
|
+
:date_time => '%m/%d/%Y at %H:%M', # 01/03/1010 at 5:30
|
271
|
+
},
|
272
|
+
:token_mapping => {
|
273
|
+
"%a" => "{short_week_day_name}",
|
274
|
+
"%A" => "{week_day_name}",
|
275
|
+
"%b" => "{short_month_name}",
|
276
|
+
"%B" => "{month_name}",
|
277
|
+
"%p" => "{am_pm}",
|
278
|
+
"%d" => "{days}",
|
279
|
+
"%e" => "{day_of_month}",
|
280
|
+
"%j" => "{year_days}",
|
281
|
+
"%m" => "{months}",
|
282
|
+
"%W" => "{week_num}",
|
283
|
+
"%w" => "{week_days}",
|
284
|
+
"%y" => "{short_years}",
|
285
|
+
"%Y" => "{years}",
|
286
|
+
"%l" => "{trimed_hour}",
|
287
|
+
"%H" => "{full_hours}",
|
288
|
+
"%I" => "{short_hours}",
|
289
|
+
"%M" => "{minutes}",
|
290
|
+
"%S" => "{seconds}",
|
291
|
+
"%s" => "{since_epoch}"
|
292
|
+
}
|
293
|
+
}
|
294
|
+
end
|
295
|
+
|
296
|
+
def enabled?
|
297
|
+
enabled
|
298
|
+
end
|
299
|
+
|
300
|
+
def disabled?
|
301
|
+
not enabled?
|
302
|
+
end
|
303
|
+
|
304
|
+
def nested_value(hash, key, default_value = nil)
|
305
|
+
parts = key.split('.')
|
306
|
+
parts.each do |part|
|
307
|
+
return default_value unless hash[part.to_sym]
|
308
|
+
hash = hash[part.to_sym]
|
309
|
+
end
|
310
|
+
hash
|
311
|
+
end
|
312
|
+
|
313
|
+
def translator_option(key)
|
314
|
+
nested_value(self.translator_options, key)
|
315
|
+
end
|
316
|
+
|
317
|
+
def cache_enabled?
|
318
|
+
cache[:enabled].nil? || Tml.config.cache[:enabled]
|
319
|
+
end
|
320
|
+
|
321
|
+
#########################################################
|
322
|
+
## Application
|
323
|
+
#########################################################
|
324
|
+
|
325
|
+
#def default_locale
|
326
|
+
# return Tml.session.application.default_locale if Tml.session.application
|
327
|
+
# @default_locale
|
328
|
+
#end
|
329
|
+
#
|
330
|
+
#def default_level
|
331
|
+
# return Tml.session.application.default_level if Tml.session.application
|
332
|
+
# @default_level
|
333
|
+
#end
|
334
|
+
|
335
|
+
def default_language
|
336
|
+
@default_language ||= begin
|
337
|
+
file = File.expand_path(File.join(File.dirname(__FILE__), '..', 'tml_core', 'languages', "#{Tml.config.default_locale}.json"))
|
338
|
+
Tml::Language.new(JSON.parse(File.read(file)))
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def default_application
|
343
|
+
@default_application ||= Tml::Application.new(:host => Tml::Api::Client::API_HOST)
|
344
|
+
end
|
345
|
+
|
346
|
+
#########################################################
|
347
|
+
## Decorations
|
348
|
+
#########################################################
|
349
|
+
|
350
|
+
def decorator_class
|
351
|
+
return Tml::Decorators::Html if @format == :html
|
352
|
+
Tml::Decorators::Default
|
353
|
+
end
|
354
|
+
|
355
|
+
def default_token_value(token_name, type = :data, format = :html)
|
356
|
+
default_tokens[format.to_sym][type.to_sym][token_name.to_sym]
|
357
|
+
end
|
358
|
+
|
359
|
+
def set_default_token(token_name, value, type = :data, format = :html)
|
360
|
+
default_tokens[format.to_sym] ||= {}
|
361
|
+
default_tokens[format.to_sym][type.to_sym] ||= {}
|
362
|
+
default_tokens[format.to_sym][type.to_sym][token_name.to_sym] = value
|
363
|
+
end
|
364
|
+
|
365
|
+
#########################################################
|
366
|
+
## Localization
|
367
|
+
#########################################################
|
368
|
+
|
369
|
+
def strftime_symbol_to_token(symbol)
|
370
|
+
localization[:token_mapping][symbol]
|
371
|
+
end
|
372
|
+
|
373
|
+
def default_day_names
|
374
|
+
localization[:default_day_names]
|
375
|
+
end
|
376
|
+
|
377
|
+
def default_day_name(index)
|
378
|
+
"" + default_day_names[index]
|
379
|
+
end
|
380
|
+
|
381
|
+
def default_abbr_day_names
|
382
|
+
localization[:default_abbr_day_names]
|
383
|
+
end
|
384
|
+
|
385
|
+
def default_abbr_day_name(index)
|
386
|
+
'' + default_abbr_day_names[index]
|
387
|
+
end
|
388
|
+
|
389
|
+
def default_month_names
|
390
|
+
localization[:default_month_names]
|
391
|
+
end
|
392
|
+
|
393
|
+
def default_month_name(index)
|
394
|
+
'' + default_month_names[index]
|
395
|
+
end
|
396
|
+
|
397
|
+
def default_abbr_month_names
|
398
|
+
localization[:default_abbr_month_names]
|
399
|
+
end
|
400
|
+
|
401
|
+
def default_abbr_month_name(index)
|
402
|
+
'' + default_abbr_month_names[index]
|
403
|
+
end
|
404
|
+
|
405
|
+
def default_date_formats
|
406
|
+
localization[:custom_date_formats]
|
407
|
+
end
|
408
|
+
|
409
|
+
end
|
410
|
+
end
|