akiva 0.1.0 → 0.1.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.
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