adhearsion 2.0.0.rc4 → 2.0.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG.md +4 -0
  2. data/adhearsion.gemspec +1 -1
  3. data/bin/ahn +0 -3
  4. data/lib/adhearsion.rb +7 -4
  5. data/lib/adhearsion/call.rb +1 -1
  6. data/lib/adhearsion/call_controller.rb +1 -0
  7. data/lib/adhearsion/call_controller/menu_dsl.rb +19 -0
  8. data/lib/adhearsion/call_controller/menu_dsl/calculated_match.rb +43 -0
  9. data/lib/adhearsion/call_controller/menu_dsl/calculated_match_collection.rb +45 -0
  10. data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +22 -0
  11. data/lib/adhearsion/call_controller/menu_dsl/match_calculator.rb +40 -0
  12. data/lib/adhearsion/call_controller/menu_dsl/menu.rb +203 -0
  13. data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +84 -0
  14. data/lib/adhearsion/call_controller/menu_dsl/range_match_calculator.rb +60 -0
  15. data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +25 -0
  16. data/lib/adhearsion/cli.rb +0 -1
  17. data/lib/adhearsion/router/route.rb +2 -2
  18. data/lib/adhearsion/version.rb +1 -1
  19. data/spec/adhearsion/call_controller/input_spec.rb +1 -1
  20. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_collection_spec.rb +60 -0
  21. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_spec.rb +61 -0
  22. data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +37 -0
  23. data/spec/adhearsion/call_controller/menu_dsl/match_calculator_spec.rb +17 -0
  24. data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +151 -0
  25. data/spec/adhearsion/call_controller/menu_dsl/menu_spec.rb +373 -0
  26. data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +32 -0
  27. data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +40 -0
  28. metadata +91 -91
  29. data/lib/adhearsion/menu_dsl.rb +0 -17
  30. data/lib/adhearsion/menu_dsl/calculated_match.rb +0 -41
  31. data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +0 -43
  32. data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +0 -20
  33. data/lib/adhearsion/menu_dsl/match_calculator.rb +0 -38
  34. data/lib/adhearsion/menu_dsl/menu.rb +0 -201
  35. data/lib/adhearsion/menu_dsl/menu_builder.rb +0 -82
  36. data/lib/adhearsion/menu_dsl/range_match_calculator.rb +0 -58
  37. data/lib/adhearsion/menu_dsl/string_match_calculator.rb +0 -23
  38. data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +0 -58
  39. data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +0 -59
  40. data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +0 -35
  41. data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +0 -15
  42. data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +0 -149
  43. data/spec/adhearsion/menu_dsl/menu_spec.rb +0 -371
  44. data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +0 -30
  45. data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +0 -38
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ class CallController
5
+ module MenuDSL
6
+
7
+ class MenuBuilder
8
+
9
+ attr_accessor :patterns, :menu_callbacks
10
+
11
+ def initialize
12
+ @patterns = []
13
+ @menu_callbacks = {}
14
+ @context = nil
15
+ end
16
+
17
+ def build(&block)
18
+ @context = eval "self", block.binding
19
+ instance_eval(&block)
20
+ end
21
+
22
+ def match(*args, &block)
23
+ payload = if block_given?
24
+ raise ArgumentError, "You cannot specify both a block and a controller name." if args.last.is_a? Class
25
+ nil
26
+ else
27
+ raise ArgumentError, "You need to provide a block or a controller name." unless args.last.is_a? Class
28
+ args.pop
29
+ end
30
+
31
+ raise ArgumentError, "You cannot call this method without patterns." if args.empty?
32
+
33
+ args.each do |pattern|
34
+ @patterns << MatchCalculator.build_with_pattern(pattern, payload, &block)
35
+ end
36
+ end
37
+
38
+ def weighted_match_calculators
39
+ @patterns
40
+ end
41
+
42
+ def has_matchers?
43
+ @patterns.size > 0
44
+ end
45
+
46
+ def execute_hook_for(symbol, input)
47
+ callback = @menu_callbacks[symbol]
48
+ return unless callback
49
+ @context.instance_exec input, &callback
50
+ end
51
+
52
+ def invalid(&block)
53
+ raise LocalJumpError, "Must supply a block!" unless block_given?
54
+ @menu_callbacks[:invalid] = block
55
+ end
56
+
57
+ def timeout(&block)
58
+ raise LocalJumpError, "Must supply a block!" unless block_given?
59
+ @menu_callbacks[:timeout] = block
60
+ end
61
+
62
+ def failure(&block)
63
+ raise LocalJumpError, "Must supply a block!" unless block_given?
64
+ @menu_callbacks[:failure] = block
65
+ end
66
+
67
+ def validator(&block)
68
+ raise LocalJumpError, "Must supply a block!" unless block_given?
69
+ @menu_callbacks[:validator] = block
70
+ end
71
+
72
+ def calculate_matches_for(result)
73
+ CalculatedMatchCollection.new.tap do |collection|
74
+ weighted_match_calculators.each do |pattern|
75
+ collection << pattern.match(result)
76
+ end
77
+ end
78
+ end
79
+
80
+ end # class MenuBuilder
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ class CallController
5
+ module MenuDSL
6
+
7
+ class RangeMatchCalculator < MatchCalculator
8
+
9
+ def initialize(pattern, match_payload)
10
+ raise unless pattern.first.kind_of?(Numeric) && pattern.last.kind_of?(Numeric)
11
+ super
12
+ end
13
+
14
+ def match(query)
15
+ numerical_query = coerce_to_numeric query
16
+ if numerical_query
17
+ exact_match = pattern.include?(numerical_query) ? query : nil
18
+ potential_matches = numbers_in_range_like numerical_query
19
+ potential_matches.reject! { |m| m.to_s == exact_match.to_s } if exact_match
20
+
21
+ new_calculated_match :query => query, :exact_matches => exact_match,
22
+ :potential_matches => potential_matches
23
+ else
24
+ CalculatedMatch.failed_match! pattern, query, match_payload
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # Returns all numbers in the range (@pattern) that +begin with+ the number given
31
+ # as the first arguement.
32
+ #
33
+ # NOTE: If you're having trouble reading what this method is actually doing. It's
34
+ # effectively a much more efficient version of this:
35
+ #
36
+ # pattern.to_a.select { |x| x.to_s.starts_with? num.to_s }.flatten
37
+ #
38
+ # Huge thanks to Dave Troy (http://davetroy.blogspot.com) for this awesomely
39
+ # efficient code!
40
+ def numbers_in_range_like(num)
41
+ return (pattern === 0 ? [0] : nil) if num == 0
42
+ raise ArgumentError unless num.kind_of?(Numeric)
43
+ Array.new.tap do |matches|
44
+ first, last = pattern.first, pattern.last
45
+ power = 0
46
+ while num < last
47
+ ones_count = 10**power - 1
48
+ range = ([num, first].max..[num + ones_count, last].min).to_a
49
+ matches.concat range
50
+ num *= 10
51
+ power += 1
52
+ end
53
+ end
54
+ end
55
+
56
+ end # class RangeMatchCalculator
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ class CallController
5
+ module MenuDSL
6
+
7
+ class StringMatchCalculator < MatchCalculator
8
+
9
+ def match(query)
10
+ args = { :query => query, :exact_matches => nil, :potential_matches => nil }
11
+
12
+ if pattern == query.to_s
13
+ args[:exact_matches] = [pattern]
14
+ elsif pattern.starts_with? query.to_s
15
+ args[:potential_matches] = [pattern]
16
+ end
17
+
18
+ new_calculated_match args
19
+ end
20
+
21
+ end # class StringMatchCalculator
22
+
23
+ end
24
+ end
25
+ end
@@ -6,6 +6,5 @@ require 'adhearsion/script_ahn_loader'
6
6
  # the rest of this script is not run.
7
7
  Adhearsion::ScriptAhnLoader.exec_script_ahn!
8
8
 
9
- require 'bundler/setup'
10
9
  require 'adhearsion'
11
10
  require 'adhearsion/cli_commands'
@@ -29,9 +29,9 @@ module Adhearsion
29
29
  target.new call
30
30
  end
31
31
 
32
- call.execute_controller controller, lambda { |call|
32
+ call.execute_controller controller, lambda { |call_actor|
33
33
  begin
34
- call.hangup
34
+ call_actor.hangup
35
35
  rescue Call::Hangup
36
36
  end
37
37
  callback.call if callback
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Adhearsion #:nodoc:
4
- VERSION = '2.0.0.rc4'
4
+ VERSION = '2.0.0.rc5'
5
5
  end
@@ -9,7 +9,7 @@ module Adhearsion
9
9
 
10
10
  describe "#play_sound_files_for_menu" do
11
11
  let(:options) { Hash.new }
12
- let(:menu_instance) { Adhearsion::MenuDSL::Menu.new(options) }
12
+ let(:menu_instance) { MenuDSL::Menu.new(options) }
13
13
  let(:sound_file) { "press a button" }
14
14
  let(:sound_files) { [sound_file] }
15
15
 
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ class CallController
7
+ module MenuDSL
8
+ describe CalculatedMatchCollection do
9
+ def mock_with_potential_matches(potential_matches)
10
+ CalculatedMatch.new :potential_matches => potential_matches
11
+ end
12
+
13
+ def mock_with_exact_matches(exact_matches)
14
+ CalculatedMatch.new :exact_matches => exact_matches
15
+ end
16
+
17
+ def mock_with_potential_and_exact_matches(potential_matches, exact_matches)
18
+ CalculatedMatch.new :potential_matches => potential_matches,
19
+ :exact_matches => exact_matches
20
+ end
21
+
22
+ it "the <<() method should collect the potential matches into the actual_potential_matches Array" do
23
+ mock_matches_array_1 = [:foo, :bar, :qaz],
24
+ mock_matches_array_2 = [10, 20, 30]
25
+ mock_matches_1 = mock_with_potential_matches mock_matches_array_1
26
+ mock_matches_2 = mock_with_potential_matches mock_matches_array_2
27
+
28
+ subject << mock_matches_1
29
+ subject.actual_potential_matches.should be == mock_matches_array_1
30
+
31
+ subject << mock_matches_2
32
+ subject.actual_potential_matches.should be == mock_matches_array_1 + mock_matches_array_2
33
+ end
34
+
35
+ it "the <<() method should collect the exact matches into the actual_exact_matches Array" do
36
+ mock_matches_array_1 = [:blam, :blargh],
37
+ mock_matches_array_2 = [5,4,3,2,1]
38
+ mock_matches_1 = mock_with_exact_matches mock_matches_array_1
39
+ mock_matches_2 = mock_with_exact_matches mock_matches_array_2
40
+
41
+ subject << mock_matches_1
42
+ subject.actual_exact_matches.should be == mock_matches_array_1
43
+
44
+ subject << mock_matches_2
45
+ subject.actual_exact_matches.should be == mock_matches_array_1 + mock_matches_array_2
46
+ end
47
+
48
+ it "if any exact matches exist, the exact_match?() method should return true" do
49
+ subject << mock_with_exact_matches([1,2,3])
50
+ subject.exact_match?.should be true
51
+ end
52
+
53
+ it "if any potential matches exist, the potential_match?() method should return true" do
54
+ subject << mock_with_potential_matches([1,2,3])
55
+ subject.potential_match?.should be true
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ class CallController
7
+ module MenuDSL
8
+ describe CalculatedMatch do
9
+ it "should make accessible the context name" do
10
+ CalculatedMatch.new(:match_payload => :foobar).match_payload.should be :foobar
11
+ end
12
+
13
+ it "should make accessible the original pattern" do
14
+ CalculatedMatch.new(:pattern => :something).pattern.should be :something
15
+ end
16
+
17
+ it "should make accessible the matched query" do
18
+ CalculatedMatch.new(:query => 123).query.should be 123
19
+ end
20
+
21
+ it "#type_of_match should return :exact, :potential, or nil" do
22
+ CalculatedMatch.new(:potential_matches => [1]).type_of_match.should be :potential
23
+ CalculatedMatch.new(:exact_matches => [3,3]).type_of_match.should be :exact
24
+ CalculatedMatch.new(:exact_matches => [8,3], :potential_matches => [0,9]).type_of_match.should be :exact
25
+ end
26
+
27
+ it "#exact_match? should return true if the match was exact" do
28
+ CalculatedMatch.new(:exact_matches => [0,3,5]).exact_match?.should be true
29
+ end
30
+
31
+ it "#potential_match? should return true if the match was exact" do
32
+ CalculatedMatch.new(:potential_matches => [88,99,77]).potential_match?.should be true
33
+ end
34
+
35
+ it "#failed_match? should return false if the match was exact" do
36
+ CalculatedMatch.new(:potential_matches => [88,99,77]).failed_match?.should be false
37
+ end
38
+
39
+ it "#exact_matches should return an array of exact matches" do
40
+ CalculatedMatch.new(:exact_matches => [0,3,5]).exact_matches.should be == [0,3,5]
41
+ end
42
+
43
+ it "#potential_matches should return an array of potential matches" do
44
+ CalculatedMatch.new(:potential_matches => [88,99,77]).potential_matches.should be == [88,99,77]
45
+ end
46
+
47
+ it "::failed_match! should return a match that *really* failed" do
48
+ failure = CalculatedMatch.failed_match! 10..20, 30, :match_payload_does_not_matter
49
+ failure.exact_match?.should_not be true
50
+ failure.potential_match?.should_not be true
51
+ failure.failed_match?.should be true
52
+ failure.type_of_match.should be nil
53
+
54
+ failure.match_payload.should be :match_payload_does_not_matter
55
+ failure.pattern.should be == (10..20)
56
+ failure.query.should be == 30
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ class CallController
7
+ module MenuDSL
8
+ describe FixnumMatchCalculator do
9
+ let(:match_payload) { :main }
10
+
11
+ it "a potential match scenario" do
12
+ calculator = FixnumMatchCalculator.new(444, match_payload)
13
+ match = calculator.match 4
14
+ match.potential_match?.should be true
15
+ match.exact_match?.should_not be true
16
+ match.potential_matches.should be == [444]
17
+ end
18
+
19
+ it "a multi-digit exact match scenario" do
20
+ calculator = FixnumMatchCalculator.new(5555, match_payload)
21
+ calculator.match(5555).exact_match?.should be true
22
+ end
23
+
24
+ it "a single-digit exact match scenario" do
25
+ calculator = FixnumMatchCalculator.new(1, match_payload)
26
+ calculator.match(1).exact_match?.should be true
27
+ end
28
+
29
+ it "the context name given to the calculator should be passed on the CalculatedMatch" do
30
+ match_payload = :icanhascheezburger
31
+ calculator = FixnumMatchCalculator.new(1337, match_payload)
32
+ calculator.match(1337).match_payload.should be match_payload
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ class CallController
7
+ module MenuDSL
8
+ describe MatchCalculator do
9
+ describe ".build_with_pattern" do
10
+ it "should return an appropriate subclass instance based on the pattern's class" do
11
+ MatchCalculator.build_with_pattern(1..2, :main).should be_an_instance_of RangeMatchCalculator
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ class CallController
7
+ module MenuDSL
8
+
9
+ describe MenuBuilder do
10
+ subject{ MenuDSL::MenuBuilder.new }
11
+
12
+ describe "#build" do
13
+ it "sets the context and instance_eval's the block" do
14
+ flexmock(subject).should_receive(:foo).with(:bar)
15
+ subject.build do
16
+ foo :bar
17
+ end
18
+ end
19
+ end#build
20
+
21
+ describe "#match" do
22
+ let(:match_block) { Proc.new() {} }
23
+
24
+ it "raises an exception if called without a CallController and no block" do
25
+ expect { subject.match 1 }.to raise_error(ArgumentError)
26
+ end
27
+
28
+ it "raises an exception if given both a payload and a block" do
29
+ expect { subject.match(1, Object) {} }.to raise_error(ArgumentError)
30
+ end
31
+
32
+ it "raises an exception if given no patterns" do
33
+ expect { subject.match() {} }.to raise_error(ArgumentError, "You cannot call this method without patterns.")
34
+ end
35
+
36
+ it "creates a pattern based on a payload" do
37
+ flexmock(MenuDSL::MatchCalculator).should_receive(:build_with_pattern).with("1", Object)
38
+ subject.match "1", Object
39
+ end
40
+
41
+ it "creates a pattern based on a block" do
42
+ flexmock(MenuDSL::MatchCalculator).should_receive(:build_with_pattern).with("1", nil, match_block)
43
+ subject.match("1", &match_block)
44
+ end
45
+
46
+ it "creates multiple patterns if multiple arguments are passed in" do
47
+ flexmock(MenuDSL::MatchCalculator).should_receive(:build_with_pattern).with(1, Object)
48
+ flexmock(MenuDSL::MatchCalculator).should_receive(:build_with_pattern).with(2, Object)
49
+ subject.match(1, 2, Object)
50
+ end
51
+ end#match
52
+
53
+ describe "#has_matchers?" do
54
+ context "with no matchers specified" do
55
+ its(:has_matchers?) { should be false }
56
+ end
57
+
58
+ context "with at least one matcher specified" do
59
+ before do
60
+ subject.match(1) {}
61
+ end
62
+
63
+ its(:has_matchers?) { should be true }
64
+ end
65
+ end
66
+
67
+ describe "#weighted_match_calculators" do
68
+ let(:expected_pattern) { MenuDSL::MatchCalculator.build_with_pattern("1", Object) }
69
+
70
+ it "returns the generated patterns" do
71
+ flexmock(MenuDSL::MatchCalculator).should_receive(:build_with_pattern).with("1", Object).and_return(expected_pattern)
72
+ subject.match("1", Object)
73
+ subject.weighted_match_calculators.should be == [expected_pattern]
74
+ end
75
+ end#weighted_match_calculators
76
+
77
+ describe "#invalid" do
78
+ let(:callback) { Proc.new() {} }
79
+
80
+ it "raises an error if not passed a block" do
81
+ expect { subject.invalid }.to raise_error(LocalJumpError)
82
+ end
83
+
84
+ it "sets the invalid callback" do
85
+ subject.invalid(&callback)
86
+ subject.menu_callbacks[:invalid].should be == callback
87
+ end
88
+ end#invalid
89
+
90
+ describe "#timeout" do
91
+ let(:callback) { Proc.new() {} }
92
+
93
+ it "raises an error if not passed a block" do
94
+ expect { subject.timeout }.to raise_error(LocalJumpError)
95
+ end
96
+
97
+ it "sets the timeout callback" do
98
+ subject.timeout(&callback)
99
+ subject.menu_callbacks[:timeout].should be == callback
100
+ end
101
+ end#timeout
102
+
103
+ describe "#failure" do
104
+ let(:callback) { Proc.new() {} }
105
+
106
+ it "raises an error if not passed a block" do
107
+ expect { subject.failure }.to raise_error(LocalJumpError)
108
+ end
109
+
110
+ it "sets the failure callback" do
111
+ subject.failure(&callback)
112
+ subject.menu_callbacks[:failure].should be == callback
113
+ end
114
+ end#failure
115
+
116
+ describe "#validator" do
117
+ let(:callback) { Proc.new() {} }
118
+
119
+ it "raises an error if not passed a block" do
120
+ expect { subject.validator }.to raise_error(LocalJumpError)
121
+ end
122
+
123
+ it "sets the invalid callback" do
124
+ subject.validator(&callback)
125
+ subject.menu_callbacks[:validator].should be == callback
126
+ end
127
+ end#invalid
128
+
129
+ describe "#execute_hook_for" do
130
+ it "executes the correct hook" do
131
+ bar = nil
132
+ subject.invalid do |baz|
133
+ bar = baz
134
+ end
135
+ subject.execute_hook_for(:invalid, "1")
136
+ bar.should be == "1"
137
+ end
138
+ end#execute_hook_for
139
+
140
+ describe "#calculate_matches_for" do
141
+ it "returns a calculated match collection" do
142
+ subject.match("1", Object)
143
+ subject.calculate_matches_for("1").should be_a CalculatedMatchCollection
144
+ end
145
+ end
146
+
147
+ end# describe MenuBuilder
148
+
149
+ end# module MenuDSL
150
+ end
151
+ end