voice_form 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +24 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +251 -0
- data/Rakefile +55 -0
- data/examples/my_component.rb +62 -0
- data/examples/simon_game_voice_form.rb +47 -0
- data/lib/voice_form.rb +3 -0
- data/lib/voice_form/form.rb +101 -0
- data/lib/voice_form/form_field.rb +209 -0
- data/lib/voice_form/form_methods.rb +69 -0
- data/spec/form_field_spec.rb +303 -0
- data/spec/form_spec.rb +232 -0
- data/spec/spec_helper.rb +28 -0
- metadata +66 -0
data/History.txt
ADDED
@@ -0,0 +1,24 @@
|
|
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
|
+
|
8
|
+
== 0.2.0 2009-01-16
|
9
|
+
* Updated to work with Adhearsion 0.8.0 release. Examples updated to match new usage.
|
10
|
+
|
11
|
+
== 0.1.1 2008-11-12
|
12
|
+
* refactored to auto include voice_form method in components, no more 'include VoiceForm'
|
13
|
+
|
14
|
+
== 0.1.0 2008-11-11
|
15
|
+
* Gemified using newgem
|
16
|
+
|
17
|
+
== 2008-11-11
|
18
|
+
* Added form field confirm callback to enable confirmation of input
|
19
|
+
|
20
|
+
== 2008-11-04
|
21
|
+
* Changed form.exit_form to form.exit
|
22
|
+
|
23
|
+
== 2008-11-03
|
24
|
+
* First commit
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Adam Meehan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
# VoiceForm
|
2
|
+
|
3
|
+
A plugin for Adhearsion to add form functionality and flow, similar to VoiceXML style forms.
|
4
|
+
|
5
|
+
By Adam Meehan (adam.meehan@gmail.com, [http://duckpunching.com/](http://duckpunching.com/))
|
6
|
+
|
7
|
+
Released under the MIT license.
|
8
|
+
|
9
|
+
## Introduction
|
10
|
+
|
11
|
+
The plugin adds form features to Adhearsion components to quickly and semantically setup data
|
12
|
+
input for your voice application. You define a form and form fields in which to collect data and
|
13
|
+
setup callbacks to instruct the caller, give feedback, confirm input and validate input.
|
14
|
+
|
15
|
+
|
16
|
+
## Install
|
17
|
+
|
18
|
+
sudo gem install adzap-voice_form --source=http://gems.github.com/
|
19
|
+
|
20
|
+
At the bottom your projects startup.rb file put
|
21
|
+
|
22
|
+
require 'voice_form'
|
23
|
+
|
24
|
+
## Example
|
25
|
+
|
26
|
+
Here is the Adhearsion example Simon game redone using voice_form:
|
27
|
+
|
28
|
+
class SimonGame
|
29
|
+
include VoiceForm
|
30
|
+
|
31
|
+
voice_form do
|
32
|
+
setup do
|
33
|
+
@number = ''
|
34
|
+
end
|
35
|
+
|
36
|
+
field(:attempt, :attempts => 1) do
|
37
|
+
prompt :play => :current_number, :bargein => false, :timeout => 2
|
38
|
+
|
39
|
+
setup do
|
40
|
+
@number << random_number
|
41
|
+
end
|
42
|
+
|
43
|
+
validate do
|
44
|
+
@attempt == @number
|
45
|
+
end
|
46
|
+
|
47
|
+
success do
|
48
|
+
call.play 'good'
|
49
|
+
form.restart
|
50
|
+
end
|
51
|
+
|
52
|
+
failure do
|
53
|
+
call.play %W[#{@number.length-1} times wrong-try-again-smarty]
|
54
|
+
@number = ''
|
55
|
+
form.restart
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def random_number
|
62
|
+
rand(10).to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_number
|
66
|
+
as_digits(@number)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
That covers most of the functionality, and hopefully it makes sense pretty much straight
|
71
|
+
away.
|
72
|
+
|
73
|
+
To start the form, in your dialplan:
|
74
|
+
|
75
|
+
For Adhearsion 0.7.999
|
76
|
+
|
77
|
+
general {
|
78
|
+
simon_game = new_simon_game
|
79
|
+
simon_game.start_voice_form(self)
|
80
|
+
}
|
81
|
+
|
82
|
+
For Adhearsion 0.8.x
|
83
|
+
|
84
|
+
general {
|
85
|
+
SimonGame.start_voice_form(self)
|
86
|
+
}
|
87
|
+
|
88
|
+
You don't have to start the form from the dialplan, but it makes it simple. You could start it from
|
89
|
+
within a component method.
|
90
|
+
|
91
|
+
All callback blocks (setup, validate, timeout etc.) are evaluated in the component scope so you can use
|
92
|
+
component methods and instance variables in them and they will work. You don't have to define any
|
93
|
+
callbacks if the field is straight forward and only depends on its length.
|
94
|
+
|
95
|
+
For a more complete example see the examples folder.
|
96
|
+
|
97
|
+
## Commands
|
98
|
+
|
99
|
+
### voice_form
|
100
|
+
|
101
|
+
The flow of the form works like a stack. So each field and do_block are executed in order until the
|
102
|
+
end of the form is reached. You can jump around the stack by using `form.goto :field_name` which
|
103
|
+
will move the *stack pointer* to the field after the current field is completed and move forward
|
104
|
+
through the form stack from that point, regardless whether a field has already been completed.
|
105
|
+
|
106
|
+
You can also use `form.restart` to start the form over from the beginning.
|
107
|
+
|
108
|
+
The form setup block is only run once and is not executed again, even with a `form.restart`.
|
109
|
+
|
110
|
+
|
111
|
+
### field
|
112
|
+
|
113
|
+
This defines the field with the name given to collect on the form. The field method can be used
|
114
|
+
in a `voice_form` or on its own inside a component method.
|
115
|
+
|
116
|
+
The options available are:
|
117
|
+
|
118
|
+
- :length - the number of digits to accept
|
119
|
+
- :min_length - minimum number of digits to accept
|
120
|
+
- :max_length - maximum number of digits to accept
|
121
|
+
- :attempts - number of tries to get a valid input
|
122
|
+
- :call - the method name for the call context if other than 'call'. Used for standalone fields not in a form.
|
123
|
+
|
124
|
+
All fields defined get an accessor method defined of the same name in the component class.
|
125
|
+
This means you can access its value using the instance variable or the accessor method inside any of
|
126
|
+
the field callbacks and in other fields on a form.
|
127
|
+
|
128
|
+
The `prompt` and `reprompt` methods are a wrapper around the input command. And as such is always
|
129
|
+
interruptable or you can _bargein_ when you want to starting keying numbers. You pass in a
|
130
|
+
hash of options to control the prompt such as:
|
131
|
+
|
132
|
+
- :play - play one or more sound files
|
133
|
+
- :speak - play TTS text (needs my Adhearsion hack for speak in input command)
|
134
|
+
- :timeout - number of seconds to wait for input. Default is 5.
|
135
|
+
- :repeats - number of attempts to use this prompt until the next one is used
|
136
|
+
- :bargein - whether to allow caller to interrupt prompt. Default is true.
|
137
|
+
|
138
|
+
The length expected for the input is taken from the options passed to the `field` method.
|
139
|
+
|
140
|
+
You can only use one of :play or :speak.
|
141
|
+
|
142
|
+
There can only be one `prompt` but you can have multiple `reprompt`s. When you add a reprompt it changes
|
143
|
+
what the prompt is if there is input the first time or the input is invalid.
|
144
|
+
|
145
|
+
|
146
|
+
### Callbacks
|
147
|
+
|
148
|
+
The available callbacks that can be defined for a field are as follows
|
149
|
+
|
150
|
+
- setup
|
151
|
+
- timeout
|
152
|
+
- validate
|
153
|
+
- invalid
|
154
|
+
- confirm
|
155
|
+
- success
|
156
|
+
- failure
|
157
|
+
|
158
|
+
Each of them takes a block which is executed as a specific point in the process of getting form input.
|
159
|
+
All of them are optional. The block for a callback is evaluated in the scope of the component so any
|
160
|
+
instance variables and component methods are available to use including the call context.
|
161
|
+
|
162
|
+
The details of each callback are as follows
|
163
|
+
|
164
|
+
#### setup
|
165
|
+
|
166
|
+
This is run once only for a field if defined before any prompts
|
167
|
+
|
168
|
+
#### timeout
|
169
|
+
|
170
|
+
This is run if no input is received or input is not of a valid length as defined by length or min_length
|
171
|
+
field options.
|
172
|
+
|
173
|
+
#### validate
|
174
|
+
|
175
|
+
This is run after input of a valid length. The validate block is where you put validation logic of the
|
176
|
+
value just input by the user. The block should return `true` if the value is valid or `false` otherwise.
|
177
|
+
If the validate callback returns false then the invalid callback will be called next.
|
178
|
+
|
179
|
+
#### invalid
|
180
|
+
|
181
|
+
The invalid callback is called if validate block returns false.
|
182
|
+
|
183
|
+
#### confirm
|
184
|
+
|
185
|
+
The confirm callback is called after the input has been validated. The confirm callback is a little different
|
186
|
+
from the others. Idea is that you return either an array or string of the audio files or TTS text, respectively,
|
187
|
+
you want to play as the prompt for confirming the value entered. The confirm block also takes a few options:
|
188
|
+
|
189
|
+
- :accept - the number to press to accept the field value entered. Default is 1.
|
190
|
+
- :reject - the number to press the reject the field value entered and try again. Default is 2.
|
191
|
+
- :attempts - the number of attempts to try to get a confirmation response. Default is 3
|
192
|
+
- :timeout - the number of seconds to wait for input after the confirmation response. Default is 3.
|
193
|
+
|
194
|
+
The value returned from the block should form the complete list of audio files or TTS text to prompt the user
|
195
|
+
including the values to accept of reject the value.
|
196
|
+
|
197
|
+
For example, in a field called my_field:
|
198
|
+
|
199
|
+
confirm(:accept => 1, :reject => 2, :attempts => 3) do
|
200
|
+
['you-entered', as_digits(@my_field), 'is-this-correct', 'press-1-accept-2-try-again'].flatten
|
201
|
+
end
|
202
|
+
|
203
|
+
The above will `play` the array of audio files as the prompt for confirmation.
|
204
|
+
|
205
|
+
confirm(:accept => 1, :reject => 2, :attempts => 3) do
|
206
|
+
"You entered #{@my_field}. Is this correct? Press 1 to accept or 2 try again."
|
207
|
+
end
|
208
|
+
|
209
|
+
The above will `speak` the string as the prompt for confirmation.
|
210
|
+
|
211
|
+
If no valid input is entered for the confirmation then another you will be reprompted to enter the field value.
|
212
|
+
|
213
|
+
|
214
|
+
### Form methods
|
215
|
+
|
216
|
+
Inside a callback you have the `form` method available. The returns the instance of the current form. The form
|
217
|
+
has some methods to allow you to perform form actions which manipulate the form stack. These actions are as follows:
|
218
|
+
|
219
|
+
#### form.goto
|
220
|
+
|
221
|
+
Inside any callback you can use the `goto` command to designate which field the form should run after the
|
222
|
+
current field. Normally the form will progress through the fields in the order defined, but a goto with shift
|
223
|
+
the current form position to the field name pass to it like so:
|
224
|
+
|
225
|
+
failure do
|
226
|
+
form.goto :other_field_name
|
227
|
+
end
|
228
|
+
|
229
|
+
The form continues from the field in the goto run each subsequent field in order. If the goto field is above the
|
230
|
+
current field then the current field will be executed again when it is reached in the stack. If the goto field
|
231
|
+
is below the current field then form will continue there, skipping whatever fields may lie between the current
|
232
|
+
and the goto field.
|
233
|
+
|
234
|
+
|
235
|
+
#### form.restart
|
236
|
+
|
237
|
+
The form may be restarted from the start at any point with `form.restart`. This will go back to the top of the
|
238
|
+
form and proceed through each field again. The form setup will not be run again however.
|
239
|
+
|
240
|
+
|
241
|
+
#### form.exit
|
242
|
+
|
243
|
+
To exit the form after the current field is complete just execute `form.exit`. The application will then be
|
244
|
+
returned to where the form was started, be it a dialplan or another form.
|
245
|
+
|
246
|
+
|
247
|
+
## Credits
|
248
|
+
|
249
|
+
Adam Meehan (adam.meehan@gmail.com, [http://duckpunching.com/](http://duckpunching.com/))
|
250
|
+
|
251
|
+
Also thanks to Jay Phillips et al. for the brilliant work on Adhearsion ([http://adhearsion.com](http://adhearsion.com)).
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
GEM = "voice_form"
|
8
|
+
GEM_VERSION = "0.3.0"
|
9
|
+
AUTHOR = "Adam Meehan"
|
10
|
+
EMAIL = "adam.meehan@gmail.com"
|
11
|
+
HOMEPAGE = "http://github.com/adzap/voice_form"
|
12
|
+
SUMMARY = "A plugin for Adhearsion to create forms in the style of the VoiceXML form element."
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.name = GEM
|
16
|
+
s.version = GEM_VERSION
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.has_rdoc = false
|
19
|
+
s.summary = SUMMARY
|
20
|
+
s.description = s.summary
|
21
|
+
s.author = AUTHOR
|
22
|
+
s.email = EMAIL
|
23
|
+
s.homepage = HOMEPAGE
|
24
|
+
s.extra_rdoc_files = ["History.txt"]
|
25
|
+
# Uncomment this to add a dependency
|
26
|
+
# s.add_dependency "foo"
|
27
|
+
|
28
|
+
s.require_path = 'lib'
|
29
|
+
s.autorequire = GEM
|
30
|
+
s.files = %w(MIT-LICENSE README.markdown Rakefile) + Dir.glob("{lib,spec,examples}/**/*")
|
31
|
+
end
|
32
|
+
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
desc "Run specs"
|
36
|
+
Spec::Rake::SpecTask.new do |t|
|
37
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
38
|
+
t.spec_opts = %w(-fs --color)
|
39
|
+
end
|
40
|
+
|
41
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
42
|
+
pkg.gem_spec = spec
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "install the gem locally"
|
46
|
+
task :install => [:package] do
|
47
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "create a gemspec file"
|
51
|
+
task :make_spec do
|
52
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
53
|
+
file.puts spec.to_ruby
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class MyComponent
|
2
|
+
include VoiceForm
|
3
|
+
|
4
|
+
MAX_AGE = 110
|
5
|
+
|
6
|
+
delegate :play, :speak, :to => :call
|
7
|
+
|
8
|
+
voice_form do
|
9
|
+
field(:age, :max_length => 3, :attempts => 3) do
|
10
|
+
prompt :speak => "Please enter your age", :timeout => 2, :repeats => 2
|
11
|
+
reprompt :speak => "Enter your age in years", :timeout => 2
|
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
|
+
|
19
|
+
invalid do
|
20
|
+
speak "You cannot be that old. Try again."
|
21
|
+
end
|
22
|
+
|
23
|
+
success do
|
24
|
+
speak "You are #{@age} years old."
|
25
|
+
end
|
26
|
+
|
27
|
+
failure do
|
28
|
+
speak "You could not enter your age. Thats a bad sign."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
do_block do
|
33
|
+
speak "Get ready for the next question."
|
34
|
+
end
|
35
|
+
|
36
|
+
field(:postcode, :length => 4, :attempts => 5) do
|
37
|
+
prompt :speak => "Please enter your 4 digit postcode", :timeout => 3
|
38
|
+
|
39
|
+
validate { @postcode[0..0] != '0' }
|
40
|
+
|
41
|
+
invalid do
|
42
|
+
if @postcode.size < 4
|
43
|
+
speak "Your postcode must 4 digits."
|
44
|
+
else
|
45
|
+
speak "Your postcode cannot start with a 0."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
success do
|
50
|
+
speak "Your postcode is #{@postcode.scan(/\d/).join(', ')}."
|
51
|
+
end
|
52
|
+
|
53
|
+
failure do
|
54
|
+
if @age.empty?
|
55
|
+
speak "Lets start over shall we."
|
56
|
+
form.restart
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -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
|