adzap-voice_form 0.2.0 → 0.3.0
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.
- data/History.txt +7 -0
- data/README.markdown +74 -32
- data/Rakefile +1 -1
- data/examples/my_component.rb +12 -8
- data/examples/simon_game_voice_form.rb +47 -0
- data/lib/voice_form/form.rb +22 -14
- data/lib/voice_form/form_field.rb +81 -56
- data/lib/voice_form/form_methods.rb +15 -9
- data/spec/form_field_spec.rb +92 -61
- data/spec/form_spec.rb +108 -89
- metadata +3 -4
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 0.3.0 2009-03-22
|
2
|
+
* Lots of refactoring for prompts. Prompts can now be a symbol for a component instance method.
|
3
|
+
* Added prompt bargein option to set whether the caller can interrupt a prompt
|
4
|
+
* Added Simon game example in the examples folder
|
5
|
+
* Added as_digits helper
|
6
|
+
* Added component start_voice_form class method to start the form without creating component instance first.
|
7
|
+
|
1
8
|
== 0.2.0 2009-01-16
|
2
9
|
* Updated to work with Adhearsion 0.8.0 release. Examples updated to match new usage.
|
3
10
|
|
data/README.markdown
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#VoiceForm
|
1
|
+
# VoiceForm
|
2
2
|
|
3
3
|
A plugin for Adhearsion to add form functionality and flow, similar to VoiceXML style forms.
|
4
4
|
|
@@ -6,7 +6,7 @@ By Adam Meehan (adam.meehan@gmail.com, [http://duckpunching.com/](http://duckpun
|
|
6
6
|
|
7
7
|
Released under the MIT license.
|
8
8
|
|
9
|
-
##Introduction
|
9
|
+
## Introduction
|
10
10
|
|
11
11
|
After developing VoiceXML (VXML) apps for quite a while and then trying Adhearsion, I found I missed
|
12
12
|
the VXML form element flow when writing components. Given that most interactions with an IVR system
|
@@ -17,7 +17,7 @@ using XML in a programmatic way, yuck! Also you are not using Ruby, so you miss
|
|
17
17
|
The plugin attempts to emulate some of the VXML form flow for use in your Adhearsion components.
|
18
18
|
|
19
19
|
|
20
|
-
##Install
|
20
|
+
## Install
|
21
21
|
|
22
22
|
sudo gem install adzap-voice_form --source=http://gems.github.com/
|
23
23
|
|
@@ -25,7 +25,7 @@ At the bottom your projects startup.rb file put
|
|
25
25
|
|
26
26
|
require 'voice_form'
|
27
27
|
|
28
|
-
##Example
|
28
|
+
## Example
|
29
29
|
|
30
30
|
I use the **speak** command in this example to give better context. The speak command is for TTS
|
31
31
|
and is currently disabled in Adhearsion. In your own application you can just use the **play**
|
@@ -34,22 +34,27 @@ command to play your sound files.
|
|
34
34
|
class MyComponent
|
35
35
|
include VoiceForm
|
36
36
|
|
37
|
+
MIN_AGE = 18
|
38
|
+
|
37
39
|
voice_form do
|
40
|
+
setup do
|
41
|
+
# Do stuff here before the form is run
|
42
|
+
end
|
38
43
|
|
39
44
|
field(:age, :max_length => 3, :attempts => 3) do
|
40
45
|
prompt :speak => "Please enter your age", :timeout => 2
|
41
46
|
reprompt :speak => "Enter your age in years", :timeout => 2
|
42
47
|
|
43
48
|
setup do
|
44
|
-
|
49
|
+
# Do stuff here before the field is run
|
45
50
|
end
|
46
51
|
|
47
52
|
timeout do
|
48
|
-
|
53
|
+
call.speak "You did not enter anything. Try again."
|
49
54
|
end
|
50
55
|
|
51
56
|
validate do
|
52
|
-
@age.to_i
|
57
|
+
@age.to_i >= MIN_AGE
|
53
58
|
end
|
54
59
|
|
55
60
|
confirm(:accept => 1, :reject => 2, :timeout => 3, :attempts => 3) do
|
@@ -57,15 +62,15 @@ command to play your sound files.
|
|
57
62
|
end
|
58
63
|
|
59
64
|
invalid do
|
60
|
-
|
65
|
+
call.speak "You must be at least #{MIN_AGE} to play. Try again."
|
61
66
|
end
|
62
67
|
|
63
68
|
success do
|
64
|
-
|
69
|
+
call.speak "You are #{@age} years old."
|
65
70
|
end
|
66
71
|
|
67
72
|
failure do
|
68
|
-
|
73
|
+
call.speak "You could not enter your age. Thats a bad sign. You might be too old."
|
69
74
|
end
|
70
75
|
end
|
71
76
|
|
@@ -74,6 +79,15 @@ command to play your sound files.
|
|
74
79
|
|
75
80
|
In your dialplan:
|
76
81
|
|
82
|
+
For Adhearsion 0.7.999
|
83
|
+
|
84
|
+
general {
|
85
|
+
my_component = new_my_component
|
86
|
+
my_component.start_voice_form(self)
|
87
|
+
}
|
88
|
+
|
89
|
+
For Adhearsion 0.8.0
|
90
|
+
|
77
91
|
general {
|
78
92
|
MyComponent.new.start_voice_form(self)
|
79
93
|
}
|
@@ -103,10 +117,6 @@ You can also use `form.restart` to start the form over from the beginning.
|
|
103
117
|
|
104
118
|
The form setup block is only run once and is not executed again, even with a `form.restart`.
|
105
119
|
|
106
|
-
The `voice_form` method takes only one option
|
107
|
-
|
108
|
-
- :call_context - to nominate the call context method if other than call context
|
109
|
-
|
110
120
|
|
111
121
|
### field
|
112
122
|
|
@@ -115,11 +125,11 @@ in a `voice_form` or on its own inside a component method.
|
|
115
125
|
|
116
126
|
The options available are:
|
117
127
|
|
118
|
-
- :length
|
119
|
-
- :min_length
|
120
|
-
- :max_length
|
121
|
-
- :attempts
|
122
|
-
- :
|
128
|
+
- :length - the number of digits to accept
|
129
|
+
- :min_length - minimum number of digits to accept
|
130
|
+
- :max_length - maximum number of digits to accept
|
131
|
+
- :attempts - number of tries to get a valid input
|
132
|
+
- :call - the method name for the call context if other than 'call'. Used for standalone fields not is a form.
|
123
133
|
|
124
134
|
All fields defined get an accessor method defined of the same name in the component class.
|
125
135
|
This means you can access its value using the instance variable or the accessor method inside any of
|
@@ -131,8 +141,9 @@ hash of options to control the prompt such as:
|
|
131
141
|
|
132
142
|
- :play - play one or more sound files
|
133
143
|
- :speak - play TTS text (needs my Adhearsion hack for speak in input command)
|
134
|
-
- :timeout - number of seconds to wait for input
|
144
|
+
- :timeout - number of seconds to wait for input. Default is 5.
|
135
145
|
- :repeats - number of attempts to use this prompt until the next one is used
|
146
|
+
- :bargein - whether to allow caller to interrupt prompt. Default is true.
|
136
147
|
|
137
148
|
The length expected for the input is taken from the options passed to the `field` method.
|
138
149
|
|
@@ -160,26 +171,26 @@ instance variables and component methods are available to use including the call
|
|
160
171
|
|
161
172
|
The details of each callback are as follows
|
162
173
|
|
163
|
-
|
174
|
+
#### setup
|
164
175
|
|
165
176
|
This is run once only for a field if defined before any prompts
|
166
177
|
|
167
|
-
|
178
|
+
#### timeout
|
168
179
|
|
169
|
-
This is run if no input is
|
180
|
+
This is run if no input is recevied or input is not of a valid length as defined by length or min_length
|
181
|
+
field options.
|
170
182
|
|
171
|
-
|
183
|
+
#### validate
|
172
184
|
|
173
185
|
This is run after input of a valid length. The validate block is where you put validation logic of the
|
174
186
|
value just input by the user. The block should return `true` if the value is valid or `false` otherwise.
|
175
187
|
If the validate callback returns false then the invalid callback will be called next.
|
176
188
|
|
177
|
-
|
189
|
+
#### invalid
|
178
190
|
|
179
|
-
The invalid callback is called if
|
180
|
-
false.
|
191
|
+
The invalid callback is called if validate block returns false.
|
181
192
|
|
182
|
-
|
193
|
+
#### confirm
|
183
194
|
|
184
195
|
The confirm callback is called after the input has been validated. The confirm callback is a little different
|
185
196
|
from the others. Idea is that you return either an array or string of the audio files or TTS text, respectively,
|
@@ -199,7 +210,7 @@ For example, in a field called my_field:
|
|
199
210
|
['you-entered', @my_field.scan(/\d/), 'is-this-correct', 'press-1-accept-2-try-again'].flatten
|
200
211
|
end
|
201
212
|
|
202
|
-
The above will `play` the array of
|
213
|
+
The above will `play` the array of audio files as the prompt for confirmation.
|
203
214
|
|
204
215
|
confirm(:accept => 1, :reject => 2, :attempts => 3) do
|
205
216
|
"You entered #{@my_field}. Is this correct? Press 1 to accept or 2 try again."
|
@@ -207,12 +218,43 @@ The above will `play` the array of audo files as the prompt for confirmation.
|
|
207
218
|
|
208
219
|
The above will `speak` the string as the prompt for confirmation.
|
209
220
|
|
210
|
-
If no valid input is entered for the confirmation
|
221
|
+
If no valid input is entered for the confirmation then another you will be reprompted to enter the field value.
|
222
|
+
|
223
|
+
|
224
|
+
### Form methods
|
225
|
+
|
226
|
+
Inside a callback you have the `form` method available. The returns the instance of the current form. The form
|
227
|
+
has some methods to allow you to perform form actions which manipulate the form stack. These actions are as follows:
|
228
|
+
|
229
|
+
#### form.goto
|
230
|
+
|
231
|
+
Inside any callback you can use the `goto` command to designate which field the form should run after the
|
232
|
+
current field. Normally the form will progress through the fields in the order defined, but a goto with shift
|
233
|
+
the current form position to the field name pass to it like so:
|
234
|
+
|
235
|
+
failure do
|
236
|
+
form.goto :other_field_name
|
237
|
+
end
|
238
|
+
|
239
|
+
The form continues from the field in the goto run each subsequent field in order. If the goto field is above the
|
240
|
+
current field then the current field will be executed again when it is reached in the stack. If the goto field
|
241
|
+
is below the current field then form will continue there, skipping whatever fields may lie between the current
|
242
|
+
and the goto field.
|
243
|
+
|
244
|
+
|
245
|
+
#### form.restart
|
246
|
+
|
247
|
+
The form may be restarted from the start at any point with `form.restart`. This will go back to the top of the
|
248
|
+
form and proceed through each field again. The form setup will not be run again however.
|
249
|
+
|
250
|
+
|
251
|
+
#### form.exit
|
211
252
|
|
212
|
-
|
253
|
+
To exit the form after the current field is complet just execute `form.exit`. The application will then be
|
254
|
+
returned to where the form was started, be it a dialplan or another form.
|
213
255
|
|
214
256
|
|
215
|
-
##Credits
|
257
|
+
## Credits
|
216
258
|
|
217
259
|
Adam Meehan (adam.meehan@gmail.com, [http://duckpunching.com/](http://duckpunching.com/))
|
218
260
|
|
data/Rakefile
CHANGED
data/examples/my_component.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
class MyComponent
|
2
2
|
include VoiceForm
|
3
3
|
|
4
|
-
|
4
|
+
MAX_AGE = 110
|
5
|
+
|
6
|
+
delegate :play, :speak, :to => :call
|
5
7
|
|
6
8
|
voice_form do
|
7
9
|
field(:age, :max_length => 3, :attempts => 3) do
|
8
10
|
prompt :speak => "Please enter your age", :timeout => 2, :repeats => 2
|
9
11
|
reprompt :speak => "Enter your age in years", :timeout => 2
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
|
13
|
+
confirm do
|
14
|
+
"Are you sure you are #{@age} years old? Press 1 to confirm, or 2 to retry."
|
15
|
+
end
|
16
|
+
|
17
|
+
validate { @age.to_i < MAX_AGE }
|
18
|
+
|
15
19
|
invalid do
|
16
|
-
speak "
|
20
|
+
speak "You cannot be that old. Try again."
|
17
21
|
end
|
18
22
|
|
19
23
|
success do
|
@@ -30,7 +34,7 @@ class MyComponent
|
|
30
34
|
end
|
31
35
|
|
32
36
|
field(:postcode, :length => 4, :attempts => 5) do
|
33
|
-
prompt :speak => "Please enter your 4 digit
|
37
|
+
prompt :speak => "Please enter your 4 digit postcode", :timeout => 3
|
34
38
|
|
35
39
|
validate { @postcode[0..0] != '0' }
|
36
40
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
methods_for :dialplan do
|
2
|
+
def simon_game_voice_form
|
3
|
+
SimonGameVoiceForm.start_voice_form(self)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class SimonGameVoiceForm
|
8
|
+
include VoiceForm
|
9
|
+
|
10
|
+
voice_form do
|
11
|
+
setup do
|
12
|
+
@number = ''
|
13
|
+
end
|
14
|
+
|
15
|
+
field(:attempt, :attempts => 1) do
|
16
|
+
prompt :play => :current_number, :bargein => false, :timeout => 2
|
17
|
+
|
18
|
+
setup do
|
19
|
+
@number << random_number
|
20
|
+
end
|
21
|
+
|
22
|
+
validate do
|
23
|
+
@attempt == @number
|
24
|
+
end
|
25
|
+
|
26
|
+
success do
|
27
|
+
call.play 'good'
|
28
|
+
form.restart
|
29
|
+
end
|
30
|
+
|
31
|
+
failure do
|
32
|
+
call.play %W[#{@number.length-1} times wrong-try-again-smarty]
|
33
|
+
@number = ''
|
34
|
+
form.restart
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def random_number
|
41
|
+
rand(10).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_number
|
45
|
+
as_digits(@number)
|
46
|
+
end
|
47
|
+
end
|
data/lib/voice_form/form.rb
CHANGED
@@ -10,7 +10,9 @@ module VoiceForm
|
|
10
10
|
@options = options
|
11
11
|
@form_stack = []
|
12
12
|
@stack_index = 0
|
13
|
-
|
13
|
+
|
14
|
+
instance_eval(&block)
|
15
|
+
raise 'A form requires at least one field defined' if fields.empty?
|
14
16
|
end
|
15
17
|
|
16
18
|
def run(component)
|
@@ -30,10 +32,7 @@ module VoiceForm
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def goto(name)
|
33
|
-
index =
|
34
|
-
form_stack.each_with_index {|slot, i|
|
35
|
-
index = i and break if form_field?(slot) && slot.name == name
|
36
|
-
}
|
35
|
+
index = field_index(name)
|
37
36
|
raise "goto failed: No form field found with name '#{name}'." unless index
|
38
37
|
@stack_index = index
|
39
38
|
end
|
@@ -43,7 +42,7 @@ module VoiceForm
|
|
43
42
|
end
|
44
43
|
|
45
44
|
def exit
|
46
|
-
@
|
45
|
+
@exit = true
|
47
46
|
end
|
48
47
|
|
49
48
|
private
|
@@ -53,7 +52,7 @@ module VoiceForm
|
|
53
52
|
end
|
54
53
|
|
55
54
|
def run_form_stack
|
56
|
-
while @stack_index < form_stack.size do
|
55
|
+
while @stack_index < form_stack.size && !@exit do
|
57
56
|
slot = form_stack[@stack_index]
|
58
57
|
@stack_index += 1
|
59
58
|
|
@@ -64,8 +63,6 @@ module VoiceForm
|
|
64
63
|
@current_field = nil
|
65
64
|
@component.instance_eval(&slot)
|
66
65
|
end
|
67
|
-
|
68
|
-
break if @exit_form
|
69
66
|
end
|
70
67
|
@stack_index = 0
|
71
68
|
@current_field = nil
|
@@ -73,11 +70,10 @@ module VoiceForm
|
|
73
70
|
|
74
71
|
def add_field_accessors
|
75
72
|
return if @accessors_added
|
76
|
-
|
77
|
-
|
78
|
-
next unless form_field?(field)
|
73
|
+
|
74
|
+
fields.keys.each do |field_name|
|
79
75
|
@component.class.class_eval do
|
80
|
-
attr_accessor
|
76
|
+
attr_accessor field_name
|
81
77
|
end
|
82
78
|
end
|
83
79
|
|
@@ -87,7 +83,19 @@ module VoiceForm
|
|
87
83
|
def form_field?(slot)
|
88
84
|
slot.is_a?(VoiceForm::FormField)
|
89
85
|
end
|
90
|
-
|
86
|
+
|
87
|
+
def fields
|
88
|
+
@fields ||= form_stack.inject({}) do |flds,s|
|
89
|
+
flds[s.name] = s if form_field?(s)
|
90
|
+
flds
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def field_index(field)
|
95
|
+
form_stack.each_with_index {|slot, i|
|
96
|
+
return i if form_field?(slot) && slot.name == field.to_sym
|
97
|
+
}
|
98
|
+
end
|
91
99
|
end
|
92
100
|
|
93
101
|
end
|
@@ -1,24 +1,28 @@
|
|
1
1
|
module VoiceForm
|
2
2
|
|
3
3
|
class FormField
|
4
|
+
cattr_accessor :default_prompt_options
|
4
5
|
attr_reader :name
|
5
|
-
|
6
|
+
|
7
|
+
self.default_prompt_options = { :bargein => true, :timeout => 5 }
|
6
8
|
|
7
9
|
def initialize(name, options, component, &block)
|
8
10
|
@name, @options, @component = name, options, component
|
9
|
-
@options.reverse_merge!(:attempts => 5, :
|
11
|
+
@options.reverse_merge!(:attempts => 5, :call => 'call')
|
10
12
|
@callbacks = {}
|
11
|
-
@
|
12
|
-
|
13
|
+
@prompt_queue = []
|
14
|
+
|
15
|
+
instance_eval(&block)
|
16
|
+
raise 'A field requires a prompt to be defined' if @prompt_queue.empty?
|
13
17
|
end
|
14
18
|
|
15
19
|
def prompt(options)
|
16
|
-
|
20
|
+
add_prompt(options.reverse_merge(self.class.default_prompt_options))
|
17
21
|
end
|
18
22
|
|
19
23
|
def reprompt(options)
|
20
|
-
raise 'A reprompt can only be used after a prompt' if @
|
21
|
-
|
24
|
+
raise 'A reprompt can only be used after a prompt' if @prompt_queue.empty?
|
25
|
+
add_prompt(options.reverse_merge(self.class.default_prompt_options))
|
22
26
|
end
|
23
27
|
|
24
28
|
def setup(&block)
|
@@ -47,29 +51,30 @@ module VoiceForm
|
|
47
51
|
|
48
52
|
def confirm(options={}, &block)
|
49
53
|
options.reverse_merge!(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
+
self.class.default_prompt_options.merge(
|
55
|
+
:attempts => 3,
|
56
|
+
:accept => 1,
|
57
|
+
:reject => 2
|
58
|
+
)
|
54
59
|
)
|
55
|
-
@confirmation_options = options.merge(:
|
60
|
+
@confirmation_options = options.merge(:message => block)
|
56
61
|
end
|
57
62
|
|
58
63
|
def run(component=nil)
|
59
64
|
@component = component if component
|
60
65
|
|
61
|
-
set_component_value('')
|
62
|
-
|
63
66
|
run_callback(:setup)
|
64
67
|
|
65
68
|
result = 1.upto(@options[:attempts]) do |attempt|
|
66
|
-
|
69
|
+
prompt = prompt_for_attempt(attempt)
|
70
|
+
|
71
|
+
@value = get_input(prompt)
|
72
|
+
|
73
|
+
unless valid_length?
|
67
74
|
run_callback(:timeout)
|
68
75
|
next
|
69
76
|
end
|
70
77
|
|
71
|
-
set_component_value @value
|
72
|
-
|
73
78
|
if input_valid?
|
74
79
|
if value_confirmed?
|
75
80
|
break 0
|
@@ -89,48 +94,43 @@ module VoiceForm
|
|
89
94
|
|
90
95
|
private
|
91
96
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
97
|
+
def get_input(prompt)
|
98
|
+
method = prompt[:method]
|
99
|
+
message = prompt[:message]
|
100
|
+
|
101
|
+
if prompt[:bargein]
|
102
|
+
prompt[method] = message
|
95
103
|
else
|
96
|
-
|
104
|
+
call.send(method, message)
|
97
105
|
end
|
98
|
-
evaluate_prompt(prompt)
|
99
|
-
end
|
100
106
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
args = [ input_options ]
|
105
|
-
length = input_options.delete(:length) || input_options.delete(:max_length)
|
106
|
-
args.unshift(length) if length
|
107
|
-
@value = call_context.input(*args)
|
107
|
+
args = [ prompt.slice(method, :timeout, :accept_key) ]
|
108
|
+
args.unshift(prompt[:length]) if prompt[:length]
|
109
|
+
call.input(*args)
|
108
110
|
end
|
109
111
|
|
110
112
|
def input_valid?
|
111
|
-
|
112
|
-
|
113
|
-
|
113
|
+
run_callback(:validate)
|
114
|
+
end
|
115
|
+
|
116
|
+
def valid_length?
|
117
|
+
!@value.empty? &&
|
118
|
+
@value.size >= minimum_length &&
|
119
|
+
@value.size <= maximum_length
|
114
120
|
end
|
115
121
|
|
116
122
|
def value_confirmed?
|
117
123
|
return true unless @confirmation_options
|
118
|
-
options = @confirmation_options.dup
|
119
124
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
prompt.
|
127
|
-
end
|
128
|
-
1.upto(options[:attempts]) do |attempt|
|
129
|
-
value = call_context.input(1, prompt)
|
130
|
-
case value
|
131
|
-
when options[:accept].to_s
|
125
|
+
prompt = evaluate_prompt(@confirmation_options)
|
126
|
+
prompt[:method] = prompt[:message].is_a?(Array) ? :play : :speak
|
127
|
+
prompt[:length] = [ prompt[:accept].to_s.size, prompt[:reject].to_s.size ].max
|
128
|
+
|
129
|
+
1.upto(prompt[:attempts]) do |attempt|
|
130
|
+
case get_input(prompt)
|
131
|
+
when prompt[:accept].to_s
|
132
132
|
return true
|
133
|
-
when
|
133
|
+
when prompt[:reject].to_s
|
134
134
|
return false
|
135
135
|
else
|
136
136
|
next
|
@@ -140,6 +140,7 @@ module VoiceForm
|
|
140
140
|
end
|
141
141
|
|
142
142
|
def run_callback(callback)
|
143
|
+
set_component_value @value
|
143
144
|
if block = @callbacks[callback]
|
144
145
|
result = @component.instance_eval(&block)
|
145
146
|
@value = get_component_value
|
@@ -154,7 +155,7 @@ module VoiceForm
|
|
154
155
|
end
|
155
156
|
|
156
157
|
def get_component_value
|
157
|
-
@component.send(
|
158
|
+
@component.send(@name)
|
158
159
|
end
|
159
160
|
|
160
161
|
def minimum_length
|
@@ -165,20 +166,44 @@ module VoiceForm
|
|
165
166
|
@options[:max_length] || @options[:length] || @value.size
|
166
167
|
end
|
167
168
|
|
168
|
-
def
|
169
|
-
@
|
169
|
+
def call
|
170
|
+
@call ||= @component.send(@options[:call])
|
170
171
|
end
|
171
172
|
|
172
|
-
def
|
173
|
+
def add_prompt(options)
|
174
|
+
method = options.has_key?(:play) ? :play : :speak
|
175
|
+
options[:message] = options.delete(method)
|
176
|
+
options[:method] = method
|
177
|
+
options[:length] = @options[:length] || @options[:max_length]
|
178
|
+
|
173
179
|
repeats = options[:repeats] || 1
|
174
|
-
@
|
180
|
+
@prompt_queue += ([options] * repeats)
|
181
|
+
end
|
182
|
+
|
183
|
+
def prompt_for_attempt(attempt)
|
184
|
+
prompt = if attempt == 1 || @prompt_queue.size == 1 then
|
185
|
+
@prompt_queue.first
|
186
|
+
else
|
187
|
+
@prompt_queue[attempt-1] || @prompt_queue.last
|
188
|
+
end
|
189
|
+
evaluate_prompt(prompt)
|
175
190
|
end
|
176
191
|
|
177
192
|
def evaluate_prompt(prompt)
|
178
|
-
|
179
|
-
message =
|
180
|
-
|
181
|
-
|
193
|
+
options = prompt.dup
|
194
|
+
message = options[:message]
|
195
|
+
|
196
|
+
message = case message
|
197
|
+
when String, Array
|
198
|
+
message
|
199
|
+
when Symbol
|
200
|
+
@component.send(message)
|
201
|
+
when Proc
|
202
|
+
@component.instance_eval(&message)
|
203
|
+
end
|
204
|
+
|
205
|
+
options[:message] = message
|
206
|
+
options
|
182
207
|
end
|
183
208
|
end
|
184
209
|
|