akiva 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +80 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE +9 -0
  8. data/README.md +131 -0
  9. data/Rakefile +5 -0
  10. data/akiva.gemspec +38 -0
  11. data/lib/akiva.rb +11 -0
  12. data/lib/akiva/brain.rb +47 -0
  13. data/lib/akiva/cli.rb +58 -0
  14. data/lib/akiva/core_brain/actions/get_answers_from_subjects_and_properties.rb +73 -0
  15. data/lib/akiva/core_brain/actions/preparators/compare_two_subjects_properties_from_comparison_adjective.rb +19 -0
  16. data/lib/akiva/core_brain/actions/preparators/extract_property_from_comparison_adjective.rb +10 -0
  17. data/lib/akiva/core_brain/actions/preparators/regroup_separated_subjects.rb +10 -0
  18. data/lib/akiva/core_brain/actions/preparators/sort_answers_by_units.rb +35 -0
  19. data/lib/akiva/core_brain/actions/template.rb +23 -0
  20. data/lib/akiva/core_brain/filters/get_answers_from_subjects_and_properties.rb +15 -0
  21. data/lib/akiva/core_brain/filters/template.rb +9 -0
  22. data/lib/akiva/core_brain/formatters/boolean_from_comparison.rb +17 -0
  23. data/lib/akiva/core_brain/formatters/list_all_answers.rb +28 -0
  24. data/lib/akiva/core_brain/formatters/template.rb +19 -0
  25. data/lib/akiva/core_brain/helpers/comparison_adjectives_to_properties.rb +151 -0
  26. data/lib/akiva/core_brain/helpers/misc.rb +28 -0
  27. data/lib/akiva/core_brain/helpers/units.rb +167 -0
  28. data/lib/akiva/core_brain/loader.rb +3 -0
  29. data/lib/akiva/question.rb +88 -0
  30. data/lib/akiva/version.rb +9 -0
  31. data/spec/lib/akiva/brain_spec.rb +89 -0
  32. data/spec/lib/akiva/core_brain/actions/get_answers_from_subjects_and_properties_spec.rb +77 -0
  33. data/spec/lib/akiva/core_brain/actions/preparators/compare_two_subjects_properties_from_comparison_adjective_spec.rb +22 -0
  34. data/spec/lib/akiva/core_brain/actions/preparators/extract_property_from_comparison_adjective_spec.rb +11 -0
  35. data/spec/lib/akiva/core_brain/actions/preparators/regroup_separated_subjects_spec.rb +11 -0
  36. data/spec/lib/akiva/core_brain/actions/preparators/sort_answers_by_units_spec.rb +36 -0
  37. data/spec/lib/akiva/core_brain/filters/get_answers_from_subjects_and_properties_spec.rb +44 -0
  38. data/spec/lib/akiva/core_brain/formatters/boolean_from_comparison_spec.rb +38 -0
  39. data/spec/lib/akiva/core_brain/formatters/list_all_answers_spec.rb +60 -0
  40. data/spec/lib/akiva/core_brain/helpers/misc_spec.rb +26 -0
  41. data/spec/lib/akiva/core_brain/helpers/units_spec.rb +141 -0
  42. data/spec/lib/akiva/core_brain/question_spec.rb +170 -0
  43. data/spec/spec_helper.rb +37 -0
  44. data/spec/support/akiva_brain_helpers.rb +13 -0
  45. data/spec/support/akiva_statements_helpers.rb +66 -0
  46. metadata +75 -16
@@ -0,0 +1,3 @@
1
+ ["helpers", "filters", "actions", "formatters"].each do |folder|
2
+ Dir[File.join(File.dirname(__FILE__), folder, "**/*.rb")].each {|file| require file }
3
+ end
@@ -0,0 +1,88 @@
1
+ module Akiva
2
+ class Question
3
+ attr_reader :content, :filter_matched, :filter_captures, :response
4
+
5
+ def initialize(content, meta = {})
6
+ @content = content
7
+ @ran_filters = @executed_action = @executed_formatter = false
8
+ @anonymous_action = AnonymousAction.new
9
+ @response = {}
10
+ end
11
+
12
+ def run_filters
13
+ return self if @ran_filters
14
+
15
+ Akiva::Brain.filters.each do |filter|
16
+ if match_data = filter[:regex].match(@content)
17
+ @filter_matched = filter
18
+ @filter_captures = Hash[match_data.names.zip(match_data.captures)]
19
+ break
20
+ end
21
+ end
22
+
23
+ @ran_filters = true
24
+ self
25
+ end
26
+
27
+ def execute_action
28
+ return self if @executed_action or @filter_matched.nil?
29
+
30
+ actions_chain = (@filter_matched[:before_action] || []) + [@filter_matched[:action]] + (@filter_matched[:after_action] || [])
31
+
32
+ @response.merge!(filter_matched: @filter_matched, filter_captures: @filter_captures, formatter: @filter_matched[:formatter], actions_chain: actions_chain)
33
+
34
+ actions_chain.each do |action_name|
35
+ if action = Akiva::Brain.actions[action_name]
36
+ if action.is_a?(Proc)
37
+ @anonymous_action.instance_exec(@response, &action)
38
+ else
39
+ @response = action.process(@response)
40
+ end
41
+ else
42
+ raise "The action #{action_name} isn't registered"
43
+ end
44
+ end
45
+
46
+ @executed_action = true
47
+ self
48
+ end
49
+
50
+ def execute_formatter
51
+ return self if @executed_formatter
52
+
53
+ if @response[:formatter]
54
+ if formatter = Akiva::Brain.formatters[@response[:formatter]]
55
+ if formatter.is_a?(Proc)
56
+ @anonymous_action.instance_exec(@response, &formatter)
57
+ else
58
+ @response = formatter.process(@response)
59
+ end
60
+ else
61
+ raise "The formatter #{@response[:formatter]} isn't registered"
62
+ end
63
+ end
64
+
65
+ @executed_formatter = true
66
+ self
67
+ end
68
+
69
+ def process
70
+ # shortcut method
71
+ run_filters
72
+ execute_action
73
+ execute_formatter
74
+
75
+ self
76
+ end
77
+
78
+ def formatted_response
79
+ # shortcut method
80
+ process
81
+ @response[:formatted]
82
+ end
83
+
84
+
85
+ class AnonymousAction; end
86
+
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ module Akiva
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].compact.join('.')
8
+ end
9
+ end
@@ -0,0 +1,89 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva Brain" do
4
+ before { reload_core_brain }
5
+ after { reload_core_brain }
6
+
7
+ describe "adding filters" do
8
+ it "is correctly reflected in the filters list" do
9
+ Akiva::Brain.update do
10
+ add_filter :action_name, /first filter/
11
+ end
12
+
13
+ Akiva::Brain::filters.first.should == {
14
+ regex: /first filter/,
15
+ action: :action_name
16
+ }
17
+
18
+ Akiva::Brain.update do
19
+ add_filter :action_name, /second filter/, other_options: [:a, :b, :c]
20
+ end
21
+
22
+ Akiva::Brain::filters.first.should == {
23
+ regex: /second filter/,
24
+ action: :action_name,
25
+ other_options: [:a, :b, :c]
26
+ }
27
+ end
28
+ end
29
+
30
+
31
+ describe "adding action" do
32
+ it "as a block is correctly reflected in the actions list" do
33
+ action = ->(response){ }
34
+
35
+ Akiva::Brain.update do
36
+ add_action :foobar, &action
37
+ end
38
+
39
+ Akiva::Brain::actions[:foobar].should == action
40
+ end
41
+
42
+ it "as an instance is correctly reflected in the actions list" do
43
+ class MyCustomAction
44
+ def process(response)
45
+ response.merge(processed: true)
46
+ end
47
+ end
48
+
49
+ action = MyCustomAction.new
50
+
51
+ Akiva::Brain.update do
52
+ add_action :foobar, action
53
+ end
54
+
55
+ Akiva::Brain::actions[:foobar].should == action
56
+ end
57
+ end
58
+
59
+ describe "adding formatters" do
60
+ it "as a block is correctly reflected in the formatters list" do
61
+ formatter = ->(response){ }
62
+
63
+ Akiva::Brain.update do
64
+ add_formatter :foobar, &formatter
65
+ end
66
+
67
+ Akiva::Brain::formatters[:foobar].should == formatter
68
+ end
69
+
70
+ it "as an instance is correctly reflected in the formatters list" do
71
+ class MyCustomFormatter
72
+ def process(response)
73
+ response.merge(processed: true)
74
+ end
75
+ end
76
+
77
+ formatter = MyCustomFormatter.new
78
+
79
+ Akiva::Brain.update do
80
+ add_formatter :foobar, formatter
81
+ end
82
+
83
+ Akiva::Brain::formatters[:foobar].should == formatter
84
+ end
85
+
86
+ end
87
+
88
+
89
+ end
@@ -0,0 +1,77 @@
1
+ require "spec_helper"
2
+
3
+ # TODO rebuild specs like the preparators
4
+
5
+ describe "Akiva CoreBrain Actions" do
6
+ it "executes on « What's the weight of an iPhone 5s »" do
7
+ stub_statements(:a0)
8
+
9
+ question = Akiva::Question.new("What's the weight of an iPhone 5s").run_filters.execute_action
10
+ question.response.should be_instance_of(Hash)
11
+ question.response.should include(subjects: ["iPhone 5s"])
12
+ question.response.should include(properties: {"iPhone 5s" => ["weight"]})
13
+ question.response.should include(answers: {"iPhone 5s" => {"weight" => "112 grams"}})
14
+ end
15
+
16
+ it "executes on « What are the weight and height of an iPhone 5s »" do
17
+ stub_statements(:a0, :a1, :a2)
18
+
19
+ question = Akiva::Question.new("What are the weight and height of an iPhone 5s").run_filters.execute_action
20
+ question.response.should be_instance_of(Hash)
21
+ question.response.should include(subjects: ["iPhone 5s"])
22
+ question.response.should include(properties: {"iPhone 5s" => ["weight", "height"]})
23
+ question.response.should include(answers: {"iPhone 5s" => {"weight" => "112 grams", "height" => "123.8 mm"}})
24
+ end
25
+
26
+ it "executes on « What are the weight of an iPhone 5s and a Galaxy S4 »" do
27
+ stub_statements(:a0, :a3, :a4)
28
+
29
+ question = Akiva::Question.new("What are the weight of an iPhone 5s and a Galaxy S4").run_filters.execute_action
30
+ question.response.should be_instance_of(Hash)
31
+ question.response.should include(subjects: ["iPhone 5s", "Galaxy S4"])
32
+ question.response.should include(properties: {"iPhone 5s" => ["weight"], "Galaxy S4" => ["weight"]})
33
+ question.response.should include(answers: {"iPhone 5s" => {"weight" => "112 grams"}, "Galaxy S4" => {"weight" => "130 grams"}})
34
+ end
35
+
36
+ it "executes on « What are the weight and height of an iPhone 5s and a Galaxy S4 »" do
37
+ stub_statements(:a0, :a1, :a2, :a3, :a4, :a5, :a6)
38
+
39
+ question = Akiva::Question.new("What are the weight and height of an iPhone 5s and a Galaxy S4").run_filters.execute_action
40
+ question.response.should be_instance_of(Hash)
41
+ question.response.should include(subjects: ["iPhone 5s", "Galaxy S4"])
42
+ question.response.should include(properties: {"iPhone 5s" => ["weight", "height"], "Galaxy S4" => ["weight", "height"]})
43
+ question.response.should include(answers: {"iPhone 5s" => {"weight" => "112 grams", "height" => "123.8 mm"}, "Galaxy S4" => {"weight" => "130 grams", "height" => "136.6 mm"}})
44
+ end
45
+
46
+ it "executes on « What is the salary of the Director of Public Affairs and Communication »" do
47
+ stub_statements(:b0)
48
+
49
+ question = Akiva::Question.new("What is the salary of the Director of Public Affairs and Communication").run_filters.execute_action
50
+ question.response.should be_instance_of(Hash)
51
+ question.response.should include(subjects: ["Director of Public Affairs and Communication"])
52
+ question.response.should include(properties: {"Director of Public Affairs and Communication" => ["salary"]})
53
+ question.response.should include(answers: {"Director of Public Affairs and Communication" => {"salary" => "$10,000"}})
54
+ end
55
+
56
+ it "executes on « What is the salary and benefits of the Director of Public Affairs and Communication »" do
57
+ stub_statements(:b0, :b1, :b2)
58
+
59
+ question = Akiva::Question.new("What is the salary and benefits of the Director of Public Affairs and Communication").run_filters.execute_action
60
+ question.response.should be_instance_of(Hash)
61
+ question.response.should include(subjects: ["Director of Public Affairs and Communication"])
62
+ question.response.should include(properties: {"Director of Public Affairs and Communication" => ["salary", "benefits"]})
63
+ question.response.should include(answers: {"Director of Public Affairs and Communication" => {"salary" => "$10,000", "benefits" => "Dental"}})
64
+ end
65
+
66
+ it "executes on « Is the iPhone 5s heavier than the Galaxy S4 »" do
67
+ stub_statements(:a0, :a3, :a4)
68
+
69
+ question = Akiva::Question.new("Is the iPhone 5s heavier than the Galaxy S4").run_filters.execute_action
70
+ question.response.should be_instance_of(Hash)
71
+ question.response.should include(subjects: ["iPhone 5s", "Galaxy S4"])
72
+ question.response.should include(properties: {"iPhone 5s" => ["weight"], "Galaxy S4" => ["weight"]})
73
+ question.response.should include(answers: {"iPhone 5s" => {"weight" => "112 grams"}, "Galaxy S4" => {"weight" => "130 grams"}})
74
+ question.response.should include(comparison_boolean_result: false)
75
+ end
76
+
77
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Actions Preparator compare_two_subjects_properties_from_comparison_adjective" do
4
+ it "updates the response correctly" do
5
+ proc = Akiva::Brain.actions[:compare_two_subjects_properties_from_comparison_adjective]
6
+ response = {
7
+ filter_captures: {"comparison_adjective" => "heavier"},
8
+ answers: {"subject1" => {"property" => "112 grams"}, "subject2" => {"property" => "2 kg"}}
9
+ }
10
+ proc.call(response)
11
+
12
+ response.should include(comparison_boolean_result: false)
13
+
14
+ response = {
15
+ filter_captures: {"comparison_adjective" => "heavier"},
16
+ answers: {"subject1" => {"property" => "112 grams"}, "subject2" => {"property" => "111 g"}}
17
+ }
18
+ proc.call(response)
19
+
20
+ response.should include(comparison_boolean_result: true)
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Actions Preparator extract_property_from_comparison_adjective" do
4
+ it "updates the response correctly" do
5
+ proc = Akiva::Brain.actions[:extract_property_from_comparison_adjective]
6
+ response = {filter_captures: {"comparison_adjective" => "heavier"}}
7
+ proc.call(response)
8
+
9
+ response.should include(defined_properties: ["weight"])
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Actions Preparator regroup_separated_subjects" do
4
+ it "updates the response correctly" do
5
+ proc = Akiva::Brain.actions[:regroup_separated_subjects]
6
+ response = {filter_captures: {"subject1" => "the iPhone 5s", "subject2" => "a Galaxy S4", "something_else" => "foobar"}}
7
+ proc.call(response)
8
+
9
+ response.should include(defined_subjects: ["iPhone 5s", "Galaxy S4"])
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Actions Preparator sort_answers_by_units" do
4
+ it "updates the response correctly" do
5
+ proc = Akiva::Brain.actions[:sort_answers_by_units]
6
+ response = {answers: {"subject1" => {"property" => "112 grams"}, "subject2" => {"property" => "2 kg"}, "subject3" => {"property" => "10 g"}}}
7
+ proc.call(response)
8
+
9
+ response.should include(sorted_answers_by_units: [
10
+ {
11
+ "subject3" => {
12
+ "property" => {
13
+ value: "10 g",
14
+ # value_with_adjusted_unit: nil
15
+ }
16
+ }
17
+ },
18
+ {
19
+ "subject1" => {
20
+ "property" => {
21
+ value: "112 g",
22
+ # value_with_adjusted_unit: nil
23
+ }
24
+ }
25
+ },
26
+ {
27
+ "subject2" => {
28
+ "property" => {
29
+ value: "2 kg",
30
+ # value_with_adjusted_unit: nil
31
+ }
32
+ }
33
+ }
34
+ ])
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Filters" do
4
+ it "matches « What's the weight of an iPhone 5s »" do
5
+ question = Akiva::Question.new("What's the weight of an iPhone 5s").run_filters
6
+ question.filter_matched.should be_instance_of(Hash)
7
+ question.filter_matched.should include(action: :get_answers_from_subjects_and_properties)
8
+ question.filter_captures["subjects"].should == "iPhone 5s"
9
+ question.filter_captures["properties"].should == "weight"
10
+ end
11
+
12
+ it "matches « What are the weight and height of an iPhone 5s »" do
13
+ question = Akiva::Question.new("What are the weight and height of an iPhone 5s").run_filters
14
+ question.filter_matched.should be_instance_of(Hash)
15
+ question.filter_matched.should include(action: :get_answers_from_subjects_and_properties)
16
+ question.filter_captures["subjects"].should == "iPhone 5s"
17
+ question.filter_captures["properties"].should == "weight and height"
18
+ end
19
+
20
+ it "matches « What are the weight, height and width of an iPhone 5s »" do
21
+ question = Akiva::Question.new("What are the weight, height and width of an iPhone 5s").run_filters
22
+ question.filter_matched.should be_instance_of(Hash)
23
+ question.filter_matched.should include(action: :get_answers_from_subjects_and_properties)
24
+ question.filter_captures["subjects"].should == "iPhone 5s"
25
+ question.filter_captures["properties"].should == "weight, height and width"
26
+ end
27
+
28
+ it "matches « What are the weight, height and width of the iPhone 5s and the Galaxy S4 »" do
29
+ question = Akiva::Question.new("What are the weight, height and width of the iPhone 5s and the Galaxy S4").run_filters
30
+ question.filter_matched.should be_instance_of(Hash)
31
+ question.filter_matched.should include(action: :get_answers_from_subjects_and_properties)
32
+ question.filter_captures["subjects"].should == "iPhone 5s and the Galaxy S4"
33
+ question.filter_captures["properties"].should == "weight, height and width"
34
+ end
35
+
36
+ it "matches « Is the iPhone 5s heavier than the Galaxy S4 »" do
37
+ question = Akiva::Question.new("Is the iPhone 5s heavier than the Galaxy S4").run_filters
38
+ question.filter_matched.should be_instance_of(Hash)
39
+ question.filter_matched.should include(action: :get_answers_from_subjects_and_properties)
40
+ question.filter_captures["subject1"].should == "the iPhone 5s"
41
+ question.filter_captures["subject2"].should == "the Galaxy S4"
42
+ question.filter_captures["comparison_adjective"].should == "heavier"
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Formatter boolean_from_comparison" do
4
+ it "updates the response correctly" do
5
+ proc = Akiva::Brain.formatters[:boolean_from_comparison]
6
+ response = {
7
+ comparison_boolean_result: true
8
+ }
9
+ proc.call(response)
10
+
11
+ response[:formatted].should == "Yes"
12
+
13
+ response = {
14
+ comparison_boolean_result: false
15
+ }
16
+ proc.call(response)
17
+
18
+ response[:formatted].should == "No"
19
+ end
20
+
21
+
22
+ describe "with the action compare_two_subjects_properties_from_comparison_adjective among the list called" do
23
+ it "updates the response correctly" do
24
+ proc = Akiva::Brain.formatters[:boolean_from_comparison]
25
+ response = {
26
+ actions_chain: [:compare_two_subjects_properties_from_comparison_adjective],
27
+ comparison_boolean_result: false,
28
+ subjects: ["iPhone 5s", "Galaxy S4"],
29
+ properties: {"iPhone 5s" => ["weight"], "Galaxy S4" => ["weight"]},
30
+ answers: {"iPhone 5s" => {"weight" => "112 grams"}, "Galaxy S4" => {"weight" => "130 grams"}}
31
+ }
32
+ proc.call(response)
33
+
34
+ response[:formatted].should == "No (iPhone 5s => 112 grams; Galaxy S4 => 130 grams)"
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe "Akiva CoreBrain Formatter list_all_answers" do
4
+ describe "with one subject and one property" do
5
+ it "updates the response correctly" do
6
+ proc = Akiva::Brain.formatters[:list_all_answers]
7
+ response = {
8
+ subjects: ["iPhone 5s"],
9
+ properties: {"iPhone 5s" => ["weight"]},
10
+ answers: {"iPhone 5s" => {"weight" => "112 grams"}}
11
+ }
12
+ proc.call(response)
13
+
14
+ response[:formatted].should == "112 grams"
15
+ end
16
+ end
17
+
18
+ describe "with one subject and multiple properties" do
19
+ it "updates the response correctly" do
20
+ proc = Akiva::Brain.formatters[:list_all_answers]
21
+ response = {
22
+ subjects: ["iPhone 5s"],
23
+ properties: {"iPhone 5s" => ["weight", "height"]},
24
+ answers: {"iPhone 5s" => {"weight" => "112 grams", "height" => "123.8 mm"}}
25
+ }
26
+ proc.call(response)
27
+
28
+ response[:formatted].should == "Weight: 112 grams, Height: 123.8 mm"
29
+ end
30
+ end
31
+
32
+ describe "with multiple subjects and one property" do
33
+ it "updates the response correctly" do
34
+ proc = Akiva::Brain.formatters[:list_all_answers]
35
+ response = {
36
+ subjects: ["iPhone 5s", "Galaxy S4"],
37
+ properties: {"iPhone 5s" => ["weight"], "Galaxy S4" => ["weight"]},
38
+ answers: {"iPhone 5s" => {"weight" => "112 grams"}, "Galaxy S4" => {"weight" => "130 grams"}}
39
+ }
40
+ proc.call(response)
41
+
42
+ response[:formatted].should == "iPhone 5s => 112 grams; Galaxy S4 => 130 grams"
43
+ end
44
+ end
45
+
46
+ describe "with multiple subjects and multiple properties" do
47
+ it "updates the response correctly" do
48
+ proc = Akiva::Brain.formatters[:list_all_answers]
49
+ response = {
50
+ subjects: ["iPhone 5s", "Galaxy S4"],
51
+ properties: {"iPhone 5s" => ["weight", "height"], "Galaxy S4" => ["weight", "height"]},
52
+ answers: {"iPhone 5s" => {"weight" => "112 grams", "height" => "123.8 mm"}, "Galaxy S4" => {"weight" => "130 grams", "height" => "136.6 mm"}}
53
+ }
54
+ proc.call(response)
55
+
56
+ response[:formatted].should == "iPhone 5s => Weight: 112 grams, Height: 123.8 mm; Galaxy S4 => Weight: 130 grams, Height: 136.6 mm"
57
+ end
58
+ end
59
+
60
+ end