tr8n_core 4.0.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 +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
|