tr8n_core 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +69 -0
- data/Rakefile +9 -0
- data/config/config.yml +34 -0
- data/config/tokens/data.yml +45 -0
- data/config/tokens/decorations.yml +37 -0
- data/lib/tr8n/application.rb +320 -0
- data/lib/tr8n/base.rb +123 -0
- data/lib/tr8n/cache.rb +144 -0
- data/lib/tr8n/cache_adapters/cdb.rb +74 -0
- data/lib/tr8n/cache_adapters/file.rb +70 -0
- data/lib/tr8n/cache_adapters/memcache.rb +91 -0
- data/lib/tr8n/cache_adapters/redis.rb +94 -0
- data/lib/tr8n/component.rb +68 -0
- data/lib/tr8n/config.rb +291 -0
- data/lib/tr8n/decorators/base.rb +35 -0
- data/lib/tr8n/decorators/default.rb +30 -0
- data/lib/tr8n/decorators/html.rb +63 -0
- data/lib/tr8n/exception.rb +26 -0
- data/lib/tr8n/language.rb +250 -0
- data/lib/tr8n/language_case.rb +116 -0
- data/lib/tr8n/language_case_rule.rb +85 -0
- data/lib/tr8n/language_context.rb +115 -0
- data/lib/tr8n/language_context_rule.rb +62 -0
- data/lib/tr8n/logger.rb +83 -0
- data/lib/tr8n/rules_engine/evaluator.rb +156 -0
- data/lib/tr8n/rules_engine/parser.rb +83 -0
- data/lib/tr8n/source.rb +95 -0
- data/lib/tr8n/tokens/data.rb +410 -0
- data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
- data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
- data/lib/tr8n/tokens/hidden.rb +48 -0
- data/lib/tr8n/tokens/method.rb +52 -0
- data/lib/tr8n/tokens/transform.rb +191 -0
- data/lib/tr8n/translation.rb +104 -0
- data/lib/tr8n/translation_key.rb +205 -0
- data/lib/tr8n/translator.rb +62 -0
- data/lib/tr8n/utils.rb +124 -0
- data/lib/tr8n_core/ext/array.rb +74 -0
- data/lib/tr8n_core/ext/date.rb +63 -0
- data/lib/tr8n_core/ext/fixnum.rb +39 -0
- data/lib/tr8n_core/ext/hash.rb +126 -0
- data/lib/tr8n_core/ext/string.rb +44 -0
- data/lib/tr8n_core/ext/time.rb +71 -0
- data/lib/tr8n_core/generators/cache/base.rb +85 -0
- data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
- data/lib/tr8n_core/generators/cache/file.rb +69 -0
- data/lib/tr8n_core/modules/logger.rb +43 -0
- data/lib/tr8n_core/version.rb +27 -0
- data/lib/tr8n_core.rb +68 -0
- data/spec/application_spec.rb +228 -0
- data/spec/base_spec.rb +19 -0
- data/spec/config_spec.rb +16 -0
- data/spec/decorator_spec.rb +10 -0
- data/spec/decorators/base_spec.rb +14 -0
- data/spec/decorators/default_spec.rb +12 -0
- data/spec/decorators/html_spec.rb +50 -0
- data/spec/fixtures/application.json +112 -0
- data/spec/fixtures/languages/en-US.json +1424 -0
- data/spec/fixtures/languages/es.json +291 -0
- data/spec/fixtures/languages/ru.json +550 -0
- data/spec/fixtures/translations/ru/basic.json +56 -0
- data/spec/fixtures/translations/ru/counters.json +43 -0
- data/spec/fixtures/translations/ru/genders.json +171 -0
- data/spec/fixtures/translations/ru/last_names.txt +200 -0
- data/spec/fixtures/translations/ru/names.json +1 -0
- data/spec/fixtures/translations/ru/names.txt +458 -0
- data/spec/helper.rb +84 -0
- data/spec/language_case_rule_spec.rb +57 -0
- data/spec/language_case_spec.rb +58 -0
- data/spec/language_context_rule_spec.rb +73 -0
- data/spec/language_context_spec.rb +331 -0
- data/spec/language_spec.rb +16 -0
- data/spec/rules_engine/evaluator_spec.rb +148 -0
- data/spec/rules_engine/parser_spec.rb +29 -0
- data/spec/tokens/data_spec.rb +160 -0
- data/spec/tokens/data_tokenizer_spec.rb +29 -0
- data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
- data/spec/tokens/hidden_spec.rb +24 -0
- data/spec/tokens/method_spec.rb +84 -0
- data/spec/tokens/transform_spec.rb +50 -0
- data/spec/translation_key_spec.rb +96 -0
- data/spec/translation_spec.rb +24 -0
- data/spec/utils_spec.rb +64 -0
- metadata +176 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Tr8n::LanguageCase < Tr8n::Base
|
25
|
+
belongs_to :language
|
26
|
+
attributes :id, :keyword, :latin_name, :native_name, :description, :application
|
27
|
+
has_many :rules
|
28
|
+
|
29
|
+
TR8N_HTML_TAGS_REGEX = /<\/?[^>]*>/
|
30
|
+
|
31
|
+
def initialize(attrs = {})
|
32
|
+
super
|
33
|
+
self.attributes[:rules] = []
|
34
|
+
if hash_value(attrs, :rules)
|
35
|
+
self.attributes[:rules] = hash_value(attrs, :rules).collect{ |rule| Tr8n::LanguageCaseRule.new(rule.merge(:language_case => self)) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_matching_rule(value, object = nil)
|
40
|
+
rules.each do |rule|
|
41
|
+
return rule if rule.evaluate(value, object)
|
42
|
+
end
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
#######################################################################################################
|
47
|
+
## Evaluation Methods
|
48
|
+
#######################################################################################################
|
49
|
+
|
50
|
+
def apply(value, object = nil, options = {})
|
51
|
+
value = value.to_s
|
52
|
+
html_tokens = value.scan(TR8N_HTML_TAGS_REGEX).uniq
|
53
|
+
sanitized_value = value.gsub(TR8N_HTML_TAGS_REGEX, "")
|
54
|
+
|
55
|
+
if application.to_sym == :phrase
|
56
|
+
words = [sanitized_value]
|
57
|
+
else
|
58
|
+
words = sanitized_value.split(/[\s\/\\]/).uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
# replace html tokens with temporary placeholders {$h1}
|
62
|
+
html_tokens.each_with_index do |html_token, index|
|
63
|
+
value = value.gsub(html_token, "{$h#{index}}")
|
64
|
+
end
|
65
|
+
|
66
|
+
# replace words with temporary placeholders {$w1}
|
67
|
+
words.each_with_index do |word, index|
|
68
|
+
value = value.gsub(word, "{$w#{index}}")
|
69
|
+
end
|
70
|
+
|
71
|
+
transformed_words = []
|
72
|
+
words.each do |word|
|
73
|
+
case_rule = find_matching_rule(word, object)
|
74
|
+
case_value = case_rule ? case_rule.apply(word) : word
|
75
|
+
transformed_words << decorate(word, case_value, case_rule, options)
|
76
|
+
end
|
77
|
+
|
78
|
+
# replace back the temporary placeholders with the html tokens
|
79
|
+
transformed_words.each_with_index do |word, index|
|
80
|
+
value = value.gsub("{$w#{index}}", word)
|
81
|
+
end
|
82
|
+
|
83
|
+
# replace back the temporary placeholders with the html tokens
|
84
|
+
html_tokens.each_with_index do |html_token, index|
|
85
|
+
value = value.gsub("{$h#{index}}", html_token)
|
86
|
+
end
|
87
|
+
|
88
|
+
value
|
89
|
+
end
|
90
|
+
|
91
|
+
#######################################################################################################
|
92
|
+
## Decoration Methods - TODO: move to decorators
|
93
|
+
#######################################################################################################
|
94
|
+
|
95
|
+
def decorate(word, case_value, case_rule, options = {})
|
96
|
+
return case_value if options[:skip_decorations]
|
97
|
+
return case_value if language.default?
|
98
|
+
return case_value unless Tr8n.config.current_translator and Tr8n.config.current_translator.inline?
|
99
|
+
|
100
|
+
"<span class='tr8n_language_case' data-case_id='#{id}' data-rule_id='#{case_rule ? case_rule.id : ''}' data-case_key='#{word.gsub("'", "\'")}'>#{case_value}</span>"
|
101
|
+
end
|
102
|
+
|
103
|
+
#######################################################################################################
|
104
|
+
## Cache Methods
|
105
|
+
#######################################################################################################
|
106
|
+
|
107
|
+
def to_cache_hash
|
108
|
+
hash = to_hash(:id, :keyword, :description, :latin_name, :native_name)
|
109
|
+
hash["rules"] = []
|
110
|
+
rules.each do |rule|
|
111
|
+
hash["rules"] << rule.to_cache_hash
|
112
|
+
end
|
113
|
+
hash
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Tr8n::LanguageCaseRule < Tr8n::Base
|
25
|
+
belongs_to :language_case
|
26
|
+
attributes :id, :description, :examples, :conditions, :conditions_expression, :operations, :operations_expression
|
27
|
+
|
28
|
+
def conditions_expression
|
29
|
+
self.attributes[:conditions_expression] ||= Tr8n::RulesEngine::Parser.new(conditions).parse
|
30
|
+
end
|
31
|
+
|
32
|
+
def operations_expression
|
33
|
+
self.attributes[:operations_expression] ||= Tr8n::RulesEngine::Parser.new(operations).parse
|
34
|
+
end
|
35
|
+
|
36
|
+
def gender_variables(object)
|
37
|
+
return {} unless conditions.index('@gender')
|
38
|
+
return {"@gender" => "unknown"} unless object
|
39
|
+
context = language_case.language.context_by_keyword(:gender)
|
40
|
+
return {"@gender" => "unknown"} unless context
|
41
|
+
context.vars(object)
|
42
|
+
end
|
43
|
+
|
44
|
+
#######################################################################################################
|
45
|
+
## Evaluation Methods
|
46
|
+
#######################################################################################################
|
47
|
+
|
48
|
+
def evaluate(value, object = nil)
|
49
|
+
return false if conditions.nil?
|
50
|
+
|
51
|
+
re = Tr8n::RulesEngine::Evaluator.new
|
52
|
+
re.evaluate(["let", "@value", value])
|
53
|
+
|
54
|
+
gender_variables(object).each do |key, value|
|
55
|
+
re.evaluate(["let", key, value])
|
56
|
+
end
|
57
|
+
|
58
|
+
re.evaluate(conditions_expression)
|
59
|
+
rescue Exception => ex
|
60
|
+
Tr8n.logger.error("Failed to evaluate language case #{conditions}: #{ex.message}")
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def apply(value)
|
65
|
+
value = value.to_s
|
66
|
+
return value if operations.nil?
|
67
|
+
|
68
|
+
re = Tr8n::RulesEngine::Evaluator.new
|
69
|
+
re.evaluate(["let", "@value", value])
|
70
|
+
|
71
|
+
re.evaluate(operations_expression)
|
72
|
+
rescue Exception => ex
|
73
|
+
Tr8n.logger.error("Failed to apply language case rule [case: #{language_case.id}] [rule: #{id}] [conds: #{conditions_expression}] [opers: #{operations_expression}]: #{ex.message}")
|
74
|
+
value
|
75
|
+
end
|
76
|
+
|
77
|
+
#######################################################################################################
|
78
|
+
## Cache Methods
|
79
|
+
#######################################################################################################
|
80
|
+
|
81
|
+
def to_cache_hash
|
82
|
+
to_hash(:id, :description, :examples, :conditions, :conditions_expression, :operations, :operations_expression)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
|
25
|
+
class Tr8n::LanguageContext < Tr8n::Base
|
26
|
+
belongs_to :language
|
27
|
+
attributes :keyword, :description, :default_key, :token_expression, :variables, :token_mapping
|
28
|
+
has_many :keys, :rules
|
29
|
+
|
30
|
+
def initialize(attrs = {})
|
31
|
+
super
|
32
|
+
|
33
|
+
self.attributes[:rules] = {}
|
34
|
+
if hash_value(attrs, :rules)
|
35
|
+
hash_value(attrs, :rules).each do |key, rule|
|
36
|
+
self.attributes[:rules][key] = Tr8n::LanguageContextRule.new(rule.merge(:language_context => self))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def config
|
42
|
+
context_rules = Tr8n.config.context_rules
|
43
|
+
hash_value(context_rules, keyword) || {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def token_expression
|
47
|
+
@token_expression ||= begin
|
48
|
+
exp = self.attributes[:token_expression]
|
49
|
+
exp = Regexp.new(exp[1..-2])
|
50
|
+
exp
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def applies_to_token?(token)
|
55
|
+
token_expression.match(token) != nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def fallback_rule
|
59
|
+
@fallback_rule ||= rules.values.detect{|rule| rule.fallback?}
|
60
|
+
end
|
61
|
+
|
62
|
+
# prepare variables for evaluation
|
63
|
+
def vars(obj)
|
64
|
+
vars = {}
|
65
|
+
|
66
|
+
variables.each do |key|
|
67
|
+
method = hash_value(config, "variables.#{key}")
|
68
|
+
unless method
|
69
|
+
vars[key] = obj
|
70
|
+
next
|
71
|
+
end
|
72
|
+
|
73
|
+
if method.is_a?(String)
|
74
|
+
if obj.is_a?(Hash)
|
75
|
+
object = hash_value(obj, 'object') || obj
|
76
|
+
if object.is_a?(Hash)
|
77
|
+
vars[key] = hash_value(object, method, :whole => true)
|
78
|
+
else
|
79
|
+
vars[key] = object.send(method)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
vars[key] = obj.send(method)
|
83
|
+
end
|
84
|
+
elsif method.is_a?(Proc)
|
85
|
+
vars[key] = method.call(obj)
|
86
|
+
else
|
87
|
+
vars[key] = obj
|
88
|
+
end
|
89
|
+
end
|
90
|
+
vars
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_matching_rule(obj)
|
94
|
+
token_vars = vars(obj)
|
95
|
+
rules.values.each do |rule|
|
96
|
+
next if rule.fallback?
|
97
|
+
return rule if rule.evaluate(token_vars)
|
98
|
+
end
|
99
|
+
fallback_rule
|
100
|
+
end
|
101
|
+
|
102
|
+
#######################################################################################################
|
103
|
+
## Cache Methods
|
104
|
+
#######################################################################################################
|
105
|
+
|
106
|
+
def to_cache_hash
|
107
|
+
hash = to_hash(:keyword, :description, :keys, :default_key, :token_expression, :variables, :token_mapping)
|
108
|
+
hash[:rules] = {}
|
109
|
+
rules.each do |key, rule|
|
110
|
+
hash[:rules][key] = rule.to_cache_hash
|
111
|
+
end
|
112
|
+
hash
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Tr8n::LanguageContextRule < Tr8n::Base
|
25
|
+
belongs_to :language_context
|
26
|
+
attributes :keyword, :description, :examples, :conditions, :conditions_expression
|
27
|
+
|
28
|
+
def fallback?
|
29
|
+
keyword.to_s.to_sym == :other
|
30
|
+
end
|
31
|
+
|
32
|
+
def conditions_expression
|
33
|
+
self.attributes[:conditions_expression] ||= Tr8n::RulesEngine::Parser.new(conditions).parse
|
34
|
+
end
|
35
|
+
|
36
|
+
#######################################################################################################
|
37
|
+
## Evaluation Methods
|
38
|
+
#######################################################################################################
|
39
|
+
|
40
|
+
def evaluate(vars = {})
|
41
|
+
return true if fallback?
|
42
|
+
|
43
|
+
re = Tr8n::RulesEngine::Evaluator.new
|
44
|
+
vars.each do |key, value|
|
45
|
+
re.evaluate(["let", key, value])
|
46
|
+
end
|
47
|
+
|
48
|
+
re.evaluate(conditions_expression)
|
49
|
+
#rescue Exception => ex
|
50
|
+
# Tr8n.logger.error("Failed to evaluate settings context rule #{conditions_expression}: #{ex.message}")
|
51
|
+
# false
|
52
|
+
end
|
53
|
+
|
54
|
+
#######################################################################################################
|
55
|
+
## Cache Methods
|
56
|
+
#######################################################################################################
|
57
|
+
|
58
|
+
def to_cache_hash
|
59
|
+
to_hash(:keyword, :description, :examples, :conditions, :conditions_expression)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/tr8n/logger.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'logger'
|
25
|
+
|
26
|
+
module Tr8n
|
27
|
+
|
28
|
+
def self.logger
|
29
|
+
@logger ||= begin
|
30
|
+
logfile_path = File.expand_path(Tr8n.config.log_path)
|
31
|
+
logfile_dir = logfile_path.split("/")[0..-2].join("/")
|
32
|
+
FileUtils.mkdir_p(logfile_dir) unless File.exist?(logfile_dir)
|
33
|
+
logfile = File.open(logfile_path, 'a')
|
34
|
+
logfile.sync = true
|
35
|
+
Tr8n::Logger.new(logfile)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Logger < ::Logger
|
40
|
+
|
41
|
+
def format_message(severity, timestamp, progname, msg)
|
42
|
+
"[#{timestamp.strftime("%D %T")}]: #{" " * stack.size}#{msg}\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
def add(severity, message = nil, progname = nil, &block)
|
46
|
+
return unless Tr8n.config.logger_enabled?
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def stack
|
51
|
+
@stack ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def trace_api_call(path, params)
|
55
|
+
debug("api: [/#{path}] #{params.inspect}")
|
56
|
+
stack.push(caller)
|
57
|
+
t0 = Time.now
|
58
|
+
if block_given?
|
59
|
+
ret = yield
|
60
|
+
end
|
61
|
+
t1 = Time.now
|
62
|
+
stack.pop
|
63
|
+
debug("call took #{t1 - t0} seconds")
|
64
|
+
ret
|
65
|
+
end
|
66
|
+
|
67
|
+
def trace(message)
|
68
|
+
debug(message)
|
69
|
+
stack.push(caller)
|
70
|
+
t0 = Time.now
|
71
|
+
if block_given?
|
72
|
+
ret = yield
|
73
|
+
end
|
74
|
+
t1 = Time.now
|
75
|
+
stack.pop
|
76
|
+
debug("execution took #{t1 - t0} seconds")
|
77
|
+
ret
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,156 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module Tr8n
|
25
|
+
module RulesEngine
|
26
|
+
|
27
|
+
class Evaluator
|
28
|
+
attr_reader :env, :vars
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@vars = {}
|
32
|
+
@env = {
|
33
|
+
# McCarthy's Elementary S-functions and Predicates
|
34
|
+
'label' => lambda { |l, r| @env[l] = @vars[l] = r },
|
35
|
+
'quote' => lambda { |expr| expr },
|
36
|
+
'car' => lambda { |list| list[1] },
|
37
|
+
'cdr' => lambda { |list| list.drop(1) },
|
38
|
+
'cons' => lambda { |e, cell| [e] + cell },
|
39
|
+
'eq' => lambda { |l, r| l == r },
|
40
|
+
'atom' => lambda { |expr| [Symbol, String, Fixnum, Float].include?(expr.class) },
|
41
|
+
'cond' => lambda { |c, t, f| evaluate(c) ? evaluate(t) : evaluate(f) },
|
42
|
+
|
43
|
+
# Tr8n Extensions
|
44
|
+
'=' => lambda { |l, r| l == r }, # ['=', 1, 2]
|
45
|
+
'!=' => lambda { |l, r| l != r }, # ['!=', 1, 2]
|
46
|
+
'<' => lambda { |l, r| l < r }, # ['<', 1, 2]
|
47
|
+
'>' => lambda { |l, r| l > r }, # ['>', 1, 2]
|
48
|
+
'+' => lambda { |l, r| l + r }, # ['+', 1, 2]
|
49
|
+
'-' => lambda { |l, r| l - r }, # ['-', 1, 2]
|
50
|
+
'*' => lambda { |l, r| l * r }, # ['*', 1, 2]
|
51
|
+
'%' => lambda { |l, r| l % r }, # ['%', 14, 10]
|
52
|
+
'mod' => lambda { |l, r| l % r }, # ['mod', '@n', 10]
|
53
|
+
'/' => lambda { |l, r| (l * 1.0) / r }, # ['/', 1, 2]
|
54
|
+
'!' => lambda { |expr| not expr }, # ['!', ['true']]
|
55
|
+
'not' => lambda { |val| not val }, # ['not', ['true']]
|
56
|
+
'&&' => lambda { |*expr| expr.all?{|e| evaluate(e)} }, # ['&&', [], [], ...]
|
57
|
+
'and' => lambda { |*expr| expr.all?{|e| evaluate(e)} }, # ['and', [], [], ...]
|
58
|
+
'||' => lambda { |*expr| expr.any?{|e| evaluate(e)} }, # ['||', [], [], ...]
|
59
|
+
'or' => lambda { |*expr| expr.any?{|e| evaluate(e)} }, # ['or', [], [], ...]
|
60
|
+
'if' => lambda { |c, t, f| evaluate(c) ? evaluate(t) : evaluate(f) }, # ['if', 'cond', 'true', 'false']
|
61
|
+
'let' => lambda { |l, r| @env[l] = @vars[l] = r }, # ['let', 'n', 5]
|
62
|
+
'true' => lambda { true }, # ['true']
|
63
|
+
'false' => lambda { false }, # ['false']
|
64
|
+
|
65
|
+
'date' => lambda { |date| Date.strptime(date, '%Y-%m-%d') }, # ['date', '2010-01-01']
|
66
|
+
'today' => lambda { Time.now.to_date }, # ['today']
|
67
|
+
'time' => lambda { |expr| Time.strptime(expr, '%Y-%m-%d %H:%M:%S') }, # ['time', '2010-01-01 10:10:05']
|
68
|
+
'now' => lambda { Time.now }, # ['now']
|
69
|
+
|
70
|
+
'append' => lambda { |l, r| r.to_s + l.to_s }, # ['append', 'world', 'hello ']
|
71
|
+
'prepend' => lambda { |l, r| l.to_s + r.to_s }, # ['prepend', 'hello ', 'world']
|
72
|
+
'match' => lambda { |search, subject| # ['match', /a/, 'abc']
|
73
|
+
search = regexp_from_string(search)
|
74
|
+
not search.match(subject).nil?
|
75
|
+
},
|
76
|
+
'in' => lambda { |values, search| # ['in', '1,2,3,5..10,20..24', '@n']
|
77
|
+
search = search.to_s.strip
|
78
|
+
values.split(',').each do |e|
|
79
|
+
if e.index('..')
|
80
|
+
bounds = e.strip.split('..')
|
81
|
+
return true if (bounds.first.strip..bounds.last.strip).include?(search)
|
82
|
+
end
|
83
|
+
return true if e.strip == search
|
84
|
+
end
|
85
|
+
false
|
86
|
+
},
|
87
|
+
'within' => lambda { |values, search| # ['within', '0..3', '@n']
|
88
|
+
bounds = values.split('..').map{|d| Integer(d)}
|
89
|
+
(bounds.first..bounds.last).include?(search)
|
90
|
+
},
|
91
|
+
'replace' => lambda { |search, replace, subject| # ['replace', '/^a/', 'v', 'abc']
|
92
|
+
# handle regular expression
|
93
|
+
if /\/i$/.match(search)
|
94
|
+
replace = replace.gsub(/\$(\d+)/, '\\\\\1') # for compatibility with Perl notation
|
95
|
+
end
|
96
|
+
search = regexp_from_string(search)
|
97
|
+
subject.gsub(search, replace)
|
98
|
+
},
|
99
|
+
'count' => lambda { |list| # ['count', '@genders']
|
100
|
+
(list.is_a?(String) ? vars[list] : list).count
|
101
|
+
},
|
102
|
+
'all' => lambda { |list, value| # ['all', '@genders', 'male']
|
103
|
+
list = (list.is_a?(String) ? vars[list] : list)
|
104
|
+
list.is_a?(Array) ? list.all?{|e| e == value} : false
|
105
|
+
},
|
106
|
+
'any' => lambda { |list, value| # ['any', '@genders', 'female']
|
107
|
+
list = (list.is_a?(String) ? vars[list] : list)
|
108
|
+
list.is_a?(Array) ? list.any?{|e| e == value} : false
|
109
|
+
},
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def regexp_from_string(str)
|
114
|
+
return Regexp.new(/#{str}/) unless /^\//.match(str)
|
115
|
+
|
116
|
+
str = str.gsub(/^\//, '')
|
117
|
+
|
118
|
+
if /\/i$/.match(str)
|
119
|
+
str = str.gsub(/\/i$/, '')
|
120
|
+
return Regexp.new(/#{str}/i)
|
121
|
+
end
|
122
|
+
|
123
|
+
str = str.gsub(/\/$/, '')
|
124
|
+
Regexp.new(/#{str}/)
|
125
|
+
end
|
126
|
+
|
127
|
+
def reset!
|
128
|
+
@vars.each do |var|
|
129
|
+
@env.delete(var)
|
130
|
+
end
|
131
|
+
@vars = {}
|
132
|
+
end
|
133
|
+
|
134
|
+
def apply(fn, args)
|
135
|
+
raise "undefined symbols #{fn}" unless @env.keys.include?(fn)
|
136
|
+
@env[fn].call(*args)
|
137
|
+
end
|
138
|
+
|
139
|
+
def evaluate(expr)
|
140
|
+
if @env['atom'].call(expr)
|
141
|
+
return @env[expr] if @env[expr]
|
142
|
+
return expr
|
143
|
+
end
|
144
|
+
|
145
|
+
fn = expr[0]
|
146
|
+
args = expr.drop(1)
|
147
|
+
|
148
|
+
unless ['quote', 'car', 'cdr', 'cond', 'if', '&&', '||', 'and', 'or', 'true', 'false', 'let', 'count', 'all', 'any'].member?(fn)
|
149
|
+
args = args.map { |a| self.evaluate(a) }
|
150
|
+
end
|
151
|
+
apply(fn, args)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|