adhearsion 2.0.0.rc4 → 2.0.0.rc5

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 (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