tr8n_core 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +69 -0
  4. data/Rakefile +9 -0
  5. data/config/config.yml +34 -0
  6. data/config/tokens/data.yml +45 -0
  7. data/config/tokens/decorations.yml +37 -0
  8. data/lib/tr8n/application.rb +320 -0
  9. data/lib/tr8n/base.rb +123 -0
  10. data/lib/tr8n/cache.rb +144 -0
  11. data/lib/tr8n/cache_adapters/cdb.rb +74 -0
  12. data/lib/tr8n/cache_adapters/file.rb +70 -0
  13. data/lib/tr8n/cache_adapters/memcache.rb +91 -0
  14. data/lib/tr8n/cache_adapters/redis.rb +94 -0
  15. data/lib/tr8n/component.rb +68 -0
  16. data/lib/tr8n/config.rb +291 -0
  17. data/lib/tr8n/decorators/base.rb +35 -0
  18. data/lib/tr8n/decorators/default.rb +30 -0
  19. data/lib/tr8n/decorators/html.rb +63 -0
  20. data/lib/tr8n/exception.rb +26 -0
  21. data/lib/tr8n/language.rb +250 -0
  22. data/lib/tr8n/language_case.rb +116 -0
  23. data/lib/tr8n/language_case_rule.rb +85 -0
  24. data/lib/tr8n/language_context.rb +115 -0
  25. data/lib/tr8n/language_context_rule.rb +62 -0
  26. data/lib/tr8n/logger.rb +83 -0
  27. data/lib/tr8n/rules_engine/evaluator.rb +156 -0
  28. data/lib/tr8n/rules_engine/parser.rb +83 -0
  29. data/lib/tr8n/source.rb +95 -0
  30. data/lib/tr8n/tokens/data.rb +410 -0
  31. data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
  32. data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
  33. data/lib/tr8n/tokens/hidden.rb +48 -0
  34. data/lib/tr8n/tokens/method.rb +52 -0
  35. data/lib/tr8n/tokens/transform.rb +191 -0
  36. data/lib/tr8n/translation.rb +104 -0
  37. data/lib/tr8n/translation_key.rb +205 -0
  38. data/lib/tr8n/translator.rb +62 -0
  39. data/lib/tr8n/utils.rb +124 -0
  40. data/lib/tr8n_core/ext/array.rb +74 -0
  41. data/lib/tr8n_core/ext/date.rb +63 -0
  42. data/lib/tr8n_core/ext/fixnum.rb +39 -0
  43. data/lib/tr8n_core/ext/hash.rb +126 -0
  44. data/lib/tr8n_core/ext/string.rb +44 -0
  45. data/lib/tr8n_core/ext/time.rb +71 -0
  46. data/lib/tr8n_core/generators/cache/base.rb +85 -0
  47. data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
  48. data/lib/tr8n_core/generators/cache/file.rb +69 -0
  49. data/lib/tr8n_core/modules/logger.rb +43 -0
  50. data/lib/tr8n_core/version.rb +27 -0
  51. data/lib/tr8n_core.rb +68 -0
  52. data/spec/application_spec.rb +228 -0
  53. data/spec/base_spec.rb +19 -0
  54. data/spec/config_spec.rb +16 -0
  55. data/spec/decorator_spec.rb +10 -0
  56. data/spec/decorators/base_spec.rb +14 -0
  57. data/spec/decorators/default_spec.rb +12 -0
  58. data/spec/decorators/html_spec.rb +50 -0
  59. data/spec/fixtures/application.json +112 -0
  60. data/spec/fixtures/languages/en-US.json +1424 -0
  61. data/spec/fixtures/languages/es.json +291 -0
  62. data/spec/fixtures/languages/ru.json +550 -0
  63. data/spec/fixtures/translations/ru/basic.json +56 -0
  64. data/spec/fixtures/translations/ru/counters.json +43 -0
  65. data/spec/fixtures/translations/ru/genders.json +171 -0
  66. data/spec/fixtures/translations/ru/last_names.txt +200 -0
  67. data/spec/fixtures/translations/ru/names.json +1 -0
  68. data/spec/fixtures/translations/ru/names.txt +458 -0
  69. data/spec/helper.rb +84 -0
  70. data/spec/language_case_rule_spec.rb +57 -0
  71. data/spec/language_case_spec.rb +58 -0
  72. data/spec/language_context_rule_spec.rb +73 -0
  73. data/spec/language_context_spec.rb +331 -0
  74. data/spec/language_spec.rb +16 -0
  75. data/spec/rules_engine/evaluator_spec.rb +148 -0
  76. data/spec/rules_engine/parser_spec.rb +29 -0
  77. data/spec/tokens/data_spec.rb +160 -0
  78. data/spec/tokens/data_tokenizer_spec.rb +29 -0
  79. data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
  80. data/spec/tokens/hidden_spec.rb +24 -0
  81. data/spec/tokens/method_spec.rb +84 -0
  82. data/spec/tokens/transform_spec.rb +50 -0
  83. data/spec/translation_key_spec.rb +96 -0
  84. data/spec/translation_spec.rb +24 -0
  85. data/spec/utils_spec.rb +64 -0
  86. 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
@@ -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