adzap-voice_form 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.
- data/History.txt +14 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +150 -0
- data/Rakefile +55 -0
- data/examples/my_component.rb +57 -0
- data/lib/voice_form.rb +11 -0
- data/lib/voice_form/form.rb +106 -0
- data/lib/voice_form/form_field.rb +184 -0
- data/lib/voice_form/form_methods.rb +63 -0
- data/spec/form_field_spec.rb +275 -0
- data/spec/form_spec.rb +213 -0
- data/spec/spec_helper.rb +28 -0
- metadata +65 -0
data/History.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
== 0.1.1 2008-11-12
|
2
|
+
* refactored to auto include voice_form method in components, no more 'include VoiceForm'
|
3
|
+
|
4
|
+
== 0.1.0 2008-11-11
|
5
|
+
* Gemified using newgem
|
6
|
+
|
7
|
+
== 2008-11-11
|
8
|
+
* Added form field confirm callback to enable confirmation of input
|
9
|
+
|
10
|
+
== 2008-11-04
|
11
|
+
* Changed form.exit_form to form.exit
|
12
|
+
|
13
|
+
== 2008-11-03
|
14
|
+
* 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,150 @@
|
|
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
|
+
After developing VoiceXML (VXML) apps for quite a while and then trying Adhearsion, I found I missed
|
12
|
+
the VXML form element flow when writing components. Given that most interactions with an IVR system
|
13
|
+
are simply prompt, input, validate and, reprompt or go the next field. This flow has been nicely
|
14
|
+
distilled into the VXML form element and its child elements. The problem with VXML is that you are
|
15
|
+
using XML in a programmatic way, yuck! Also you are not using Ruby, so you miss out on its awesomeness.
|
16
|
+
|
17
|
+
The plugin attempts to emulate some of the VXML form flow for use in your Adhearsion components.
|
18
|
+
|
19
|
+
|
20
|
+
##Install
|
21
|
+
|
22
|
+
sudo gem install adzap-voice_form --source=http://gems.github.com/
|
23
|
+
|
24
|
+
At the bottom your projects startup.rb file put
|
25
|
+
|
26
|
+
require 'voice_form'
|
27
|
+
|
28
|
+
##Example
|
29
|
+
|
30
|
+
I use the **speak** command in this example to give better context. The speak command is for TTS
|
31
|
+
and is currently disabled in Adhearsion. In your own application you can just use the **play**
|
32
|
+
command to play your sound files.
|
33
|
+
|
34
|
+
class MyComponent
|
35
|
+
include VoiceForm
|
36
|
+
add_call_context :as => :call_context
|
37
|
+
|
38
|
+
voice_form do
|
39
|
+
|
40
|
+
field(:age, :max_length => 3, :attempts => 3) do
|
41
|
+
prompt :speak => "Please enter your age", :timeout => 2
|
42
|
+
reprompt :speak => "Enter your age in years", :timeout => 2
|
43
|
+
|
44
|
+
setup do
|
45
|
+
@max_age = 110
|
46
|
+
end
|
47
|
+
|
48
|
+
timeout do
|
49
|
+
call_context.speak "You did not enter anything. Try again."
|
50
|
+
end
|
51
|
+
|
52
|
+
validate do
|
53
|
+
@age.to_i <= @max_age
|
54
|
+
end
|
55
|
+
|
56
|
+
invalid do
|
57
|
+
call_context.speak "Your age must be less than #{@max_age}. Try again."
|
58
|
+
end
|
59
|
+
|
60
|
+
success do
|
61
|
+
call_context.speak "You are #{@age} years old."
|
62
|
+
end
|
63
|
+
|
64
|
+
failure do
|
65
|
+
call_context.speak "You could not enter your age. Thats a bad sign."
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
In your dialplan:
|
73
|
+
|
74
|
+
general {
|
75
|
+
my_component = new_my_component
|
76
|
+
my_component.start_voice_form
|
77
|
+
}
|
78
|
+
|
79
|
+
That covers most of the functionality, and hopefully it makes sense pretty much straight
|
80
|
+
away.
|
81
|
+
|
82
|
+
You don't have to start the form from the dialplan, but it makes it simple. You could start it from
|
83
|
+
within a component method.
|
84
|
+
|
85
|
+
All blocks (setup, validate, do_block etc.) are evaluated in the component scope so you can use
|
86
|
+
component methods and instance variables in them and they will work.
|
87
|
+
|
88
|
+
For a more complete example see the examples folder.
|
89
|
+
|
90
|
+
### voice_form
|
91
|
+
|
92
|
+
The flow of the form works like a stack. So each field and do_block are executed in order until the
|
93
|
+
end of the form is reached. You can jump around the stack by using `form.goto :field_name` which
|
94
|
+
will move the *stack pointer* to the field after the current field is completed and move forward
|
95
|
+
through the form stack from that point, regardless whether a field has already been completed.
|
96
|
+
|
97
|
+
You can also use `form.restart` to start the form over from the beginning.
|
98
|
+
|
99
|
+
The form setup block is only run once and is not executed again, even with a `form.restart`.
|
100
|
+
|
101
|
+
The `voice_form` method takes only one option
|
102
|
+
|
103
|
+
- :call_context - to nominate the call context method if other than call context
|
104
|
+
|
105
|
+
|
106
|
+
### field
|
107
|
+
|
108
|
+
This defines the field the with name given to collect on the form. The field method can be used
|
109
|
+
in a `voice_form` or on its own inside a component method.
|
110
|
+
|
111
|
+
The options available are:
|
112
|
+
|
113
|
+
- :length - the number of digits to accept
|
114
|
+
- :min_length - minimum number of digits to accept
|
115
|
+
- :max_length - maximum number of digits to accept
|
116
|
+
- :attempts - number of tries to get a valid input
|
117
|
+
- :call_context - the method name for the call context if other than 'call_context'
|
118
|
+
|
119
|
+
All fields defined get an accessor method defined of the same name in the component class.
|
120
|
+
This means you can access its value using the instance variable or the accessor method inside any of
|
121
|
+
the field callbacks and in other fields on a form.
|
122
|
+
|
123
|
+
The `prompt` and `reprompt` methods are a wrapper around the input command. And as such is always
|
124
|
+
interruptable or you can _bargein_ when you want to starting keying numbers. You pass in a
|
125
|
+
hash of options to control the prompt such as:
|
126
|
+
|
127
|
+
- :play - play one or more sound files
|
128
|
+
- :speak - play TTS text (needs my Adhearsion hack for speak in input command)
|
129
|
+
- :timeout - number of seconds to wait for input
|
130
|
+
- :repeats - number of attempts to use this prompt until the next one is used
|
131
|
+
|
132
|
+
The length expected for the input is taken from the options passed to the `field` method.
|
133
|
+
|
134
|
+
You can only use one of :play or :speak.
|
135
|
+
|
136
|
+
There can only be one `prompt` but you can have multiple `reprompt`s. When you add a reprompt it changes
|
137
|
+
what the prompt is if there is input the first time or the input is invalid.
|
138
|
+
|
139
|
+
|
140
|
+
TODO: Add specific info for callback and option.
|
141
|
+
|
142
|
+
|
143
|
+
TODO: More docs
|
144
|
+
|
145
|
+
|
146
|
+
##Credits
|
147
|
+
|
148
|
+
Adam Meehan (adam.meehan@gmail.com, [http://duckpunching.com/](http://duckpunching.com/))
|
149
|
+
|
150
|
+
Also thanks to Jay Phillips for his 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.1.0"
|
9
|
+
AUTHOR = "Adam Meehan"
|
10
|
+
EMAIL = "adam.meehan@gmail.com"
|
11
|
+
HOMEPAGE = "http://github.com/adzap/voice_form"
|
12
|
+
SUMMARY = "A DSL 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,57 @@
|
|
1
|
+
class MyComponent
|
2
|
+
include VoiceForm
|
3
|
+
add_call_context :as => :call_context
|
4
|
+
|
5
|
+
voice_form do
|
6
|
+
field(:age, :max_length => 3, :attempts => 3) do
|
7
|
+
prompt :speak => "Please enter your age", :timeout => 2, :repeats => 2
|
8
|
+
reprompt :speak => "Enter your age in years", :timeout => 2
|
9
|
+
|
10
|
+
setup { @max_age = 110 }
|
11
|
+
|
12
|
+
validate { @age.to_i < @max_age }
|
13
|
+
|
14
|
+
invalid do
|
15
|
+
call_context.speak "Your age must be less than #{@max_age}. Try again."
|
16
|
+
end
|
17
|
+
|
18
|
+
success do
|
19
|
+
call_context.speak "You are #{@age} years old."
|
20
|
+
end
|
21
|
+
|
22
|
+
failure do
|
23
|
+
call_context.speak "You could not enter your age. Thats a bad sign."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
do_block do
|
28
|
+
call_context.speak "Get ready for the next question."
|
29
|
+
end
|
30
|
+
|
31
|
+
field(:postcode, :length => 4, :attempts => 5) do
|
32
|
+
prompt :speak => "Please enter your 4 digit post code", :timeout => 3
|
33
|
+
|
34
|
+
validate { @postcode[0..0] != '0' }
|
35
|
+
|
36
|
+
invalid do
|
37
|
+
if @postcode.size < 4
|
38
|
+
call_context.speak "Your postcode must 4 digits."
|
39
|
+
else
|
40
|
+
call_context.speak "Your postcode cannot start with a 0."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
success do
|
45
|
+
call_context.speak "Your postcode is #{@postcode.scan(/\d/).join(', ')}."
|
46
|
+
end
|
47
|
+
|
48
|
+
failure do
|
49
|
+
if @age.empty?
|
50
|
+
call_context.speak "Lets start over shall we."
|
51
|
+
form.restart
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
data/lib/voice_form.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'voice_form/form_methods'
|
2
|
+
require 'voice_form/form'
|
3
|
+
require 'voice_form/form_field'
|
4
|
+
|
5
|
+
Adhearsion::Components::Behavior.module_eval do
|
6
|
+
include VoiceForm::FormMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
Adhearsion::Components::Behavior::ClassMethods.module_eval do
|
10
|
+
include VoiceForm::MacroMethods
|
11
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module VoiceForm
|
2
|
+
|
3
|
+
class Form
|
4
|
+
include VoiceForm::FormMethods
|
5
|
+
|
6
|
+
attr_accessor :form_stack
|
7
|
+
attr_reader :current_field
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
@options = options
|
11
|
+
|
12
|
+
@form_stack = []
|
13
|
+
@stack_index = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(component)
|
17
|
+
@component = component
|
18
|
+
|
19
|
+
alias_call_context
|
20
|
+
|
21
|
+
add_field_accessors
|
22
|
+
|
23
|
+
run_setup
|
24
|
+
|
25
|
+
run_form_stack
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup(&block)
|
29
|
+
@setup = block
|
30
|
+
end
|
31
|
+
|
32
|
+
def do_block(&block)
|
33
|
+
form_stack << block
|
34
|
+
end
|
35
|
+
|
36
|
+
def goto(name)
|
37
|
+
index = nil
|
38
|
+
form_stack.each_with_index {|slot, i|
|
39
|
+
index = i and break if form_field?(slot) && slot.name == name
|
40
|
+
}
|
41
|
+
raise "goto failed: No form field found with name '#{name}'." unless index
|
42
|
+
@stack_index = index
|
43
|
+
end
|
44
|
+
|
45
|
+
def restart
|
46
|
+
@stack_index = 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def exit
|
50
|
+
@exit_form = true
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def run_setup
|
56
|
+
@component.instance_eval(&@setup) if @setup
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_form_stack
|
60
|
+
while @stack_index < form_stack.size do
|
61
|
+
slot = form_stack[@stack_index]
|
62
|
+
@stack_index += 1
|
63
|
+
|
64
|
+
if form_field?(slot)
|
65
|
+
@current_field = slot.name
|
66
|
+
slot.run(@component)
|
67
|
+
else
|
68
|
+
@current_field = nil
|
69
|
+
@component.instance_eval(&slot)
|
70
|
+
end
|
71
|
+
|
72
|
+
break if @exit_form
|
73
|
+
end
|
74
|
+
@stack_index = 0
|
75
|
+
@current_field = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_field_accessors
|
79
|
+
return if @accessors_added
|
80
|
+
|
81
|
+
form_stack.each do |field|
|
82
|
+
next unless form_field?(field)
|
83
|
+
@component.class.class_eval do
|
84
|
+
attr_accessor field.name
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@accessors_added = true
|
89
|
+
end
|
90
|
+
|
91
|
+
def form_field?(slot)
|
92
|
+
slot.is_a?(VoiceForm::FormField)
|
93
|
+
end
|
94
|
+
|
95
|
+
def alias_call_context
|
96
|
+
# hack to avoid setting the call_context in each field for different context name
|
97
|
+
if context_name = @options[:call_context] && !@component.respond_to?(:call_context)
|
98
|
+
@component.class_eval do
|
99
|
+
alias_method :call_context, context_name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module VoiceForm
|
2
|
+
|
3
|
+
class FormField
|
4
|
+
attr_reader :name
|
5
|
+
attr_accessor :prompts
|
6
|
+
|
7
|
+
def initialize(name, options, component)
|
8
|
+
@name, @options, @component = name, options, component
|
9
|
+
@options.reverse_merge!(:attempts => 5, :call_context => 'call_context')
|
10
|
+
@callbacks = {}
|
11
|
+
@prompts = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def prompt(options)
|
15
|
+
add_prompts(options.reverse_merge(:timeout => 5))
|
16
|
+
end
|
17
|
+
|
18
|
+
def reprompt(options)
|
19
|
+
raise 'A reprompt can only be used after a prompt' if @prompts.empty?
|
20
|
+
add_prompts(options.reverse_merge(:timeout => 5))
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup(&block)
|
24
|
+
@callbacks[:setup] = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate(&block)
|
28
|
+
@callbacks[:validate] = block
|
29
|
+
end
|
30
|
+
|
31
|
+
def invalid(&block)
|
32
|
+
@callbacks[:invalid] = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def timeout(&block)
|
36
|
+
@callbacks[:timeout] = block
|
37
|
+
end
|
38
|
+
|
39
|
+
def success(&block)
|
40
|
+
@callbacks[:success] = block
|
41
|
+
end
|
42
|
+
|
43
|
+
def failure(&block)
|
44
|
+
@callbacks[:failure] = block
|
45
|
+
end
|
46
|
+
|
47
|
+
def confirm(options={}, &block)
|
48
|
+
options.reverse_merge!(
|
49
|
+
:attempts => 3,
|
50
|
+
:accept => 1,
|
51
|
+
:reject => 2,
|
52
|
+
:timeout => 3
|
53
|
+
)
|
54
|
+
@confirmation_options = options.merge(:block => block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def run(component=nil)
|
58
|
+
@component = component if component
|
59
|
+
|
60
|
+
set_component_value('')
|
61
|
+
|
62
|
+
run_callback(:setup)
|
63
|
+
|
64
|
+
result = 1.upto(@options[:attempts]) do |attempt|
|
65
|
+
if get_input(attempt).empty?
|
66
|
+
run_callback(:timeout)
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
set_component_value @value
|
71
|
+
|
72
|
+
if input_valid?
|
73
|
+
if value_confirmed?
|
74
|
+
break 0
|
75
|
+
else
|
76
|
+
next
|
77
|
+
end
|
78
|
+
else
|
79
|
+
run_callback(:invalid)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
if result == 0
|
83
|
+
run_callback(:success)
|
84
|
+
else
|
85
|
+
run_callback(:failure)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def prompt_for_attempt(attempt)
|
92
|
+
prompt = if attempt == 1 || @prompts.size == 1 then
|
93
|
+
@prompts.first
|
94
|
+
else
|
95
|
+
@prompts[attempt-1] || @prompts.last
|
96
|
+
end
|
97
|
+
evaluate_prompt(prompt)
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_input(attempt)
|
101
|
+
input_options = @options.dup
|
102
|
+
input_options.merge!(prompt_for_attempt(attempt))
|
103
|
+
args = [ input_options ]
|
104
|
+
length = input_options.delete(:length) || input_options.delete(:max_length)
|
105
|
+
args.unshift(length) if length
|
106
|
+
@value = call_context.input(*args)
|
107
|
+
end
|
108
|
+
|
109
|
+
def input_valid?
|
110
|
+
@value.size >= minimum_length &&
|
111
|
+
@value.size <= maximum_length &&
|
112
|
+
run_callback(:validate)
|
113
|
+
end
|
114
|
+
|
115
|
+
def value_confirmed?
|
116
|
+
return true unless @confirmation_options
|
117
|
+
options = @confirmation_options.dup
|
118
|
+
|
119
|
+
if block = options.delete(:block)
|
120
|
+
message = @component.instance_eval(&block)
|
121
|
+
prompt = case message
|
122
|
+
when Array: {:play => message}
|
123
|
+
when String: {:speak => message}
|
124
|
+
end
|
125
|
+
prompt.merge!(options.slice(:timeout))
|
126
|
+
end
|
127
|
+
1.upto(options[:attempts]) do |attempt|
|
128
|
+
value = call_context.input(1, prompt)
|
129
|
+
case value
|
130
|
+
when options[:accept].to_s
|
131
|
+
return true
|
132
|
+
when options[:reject].to_s
|
133
|
+
return false
|
134
|
+
else
|
135
|
+
next
|
136
|
+
end
|
137
|
+
end
|
138
|
+
false
|
139
|
+
end
|
140
|
+
|
141
|
+
def run_callback(callback)
|
142
|
+
if block = @callbacks[callback]
|
143
|
+
result = @component.instance_eval(&block)
|
144
|
+
@value = get_component_value
|
145
|
+
result
|
146
|
+
else
|
147
|
+
true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def set_component_value(value)
|
152
|
+
@component.send("#{@name}=", @value)
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_component_value
|
156
|
+
@component.send("#{@name}")
|
157
|
+
end
|
158
|
+
|
159
|
+
def minimum_length
|
160
|
+
@options[:min_length] || @options[:length] || 1
|
161
|
+
end
|
162
|
+
|
163
|
+
def maximum_length
|
164
|
+
@options[:max_length] || @options[:length] || @value.size
|
165
|
+
end
|
166
|
+
|
167
|
+
def call_context
|
168
|
+
@call_context ||= @component.send(@options[:call_context])
|
169
|
+
end
|
170
|
+
|
171
|
+
def add_prompts(options)
|
172
|
+
repeats = options[:repeats] || 1
|
173
|
+
@prompts += ([options] * repeats)
|
174
|
+
end
|
175
|
+
|
176
|
+
def evaluate_prompt(prompt)
|
177
|
+
key = prompt.has_key?(:play) ? :play : :speak
|
178
|
+
message = prompt[key]
|
179
|
+
message = @component.instance_eval(&message) if message.is_a?(Proc)
|
180
|
+
prompt.merge(key => message)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module VoiceForm
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend MacroMethods
|
5
|
+
base.class_eval do
|
6
|
+
include FormMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module MacroMethods
|
11
|
+
|
12
|
+
def voice_form(options={}, &block)
|
13
|
+
raise "Voice form requires block" unless block_given?
|
14
|
+
|
15
|
+
self.class_eval do
|
16
|
+
include InstanceMethods
|
17
|
+
|
18
|
+
cattr_accessor :voice_form_options
|
19
|
+
attr_accessor :form
|
20
|
+
end
|
21
|
+
|
22
|
+
self.voice_form_options = [options, block]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
|
29
|
+
def start_voice_form
|
30
|
+
raise "No voice form defined" unless self.voice_form_options
|
31
|
+
self.form = VoiceForm::Form.new(self.class.voice_form_options[0])
|
32
|
+
self.form.instance_eval(&self.class.voice_form_options[1])
|
33
|
+
self.form.run(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
module FormMethods
|
39
|
+
|
40
|
+
# Can be used in a form or stand-alone in a component method
|
41
|
+
def field(field_name, options={}, &block)
|
42
|
+
raise 'Field require a block' unless block_given?
|
43
|
+
|
44
|
+
form_field = VoiceForm::FormField.new(field_name, options, self)
|
45
|
+
|
46
|
+
form_field.instance_eval(&block)
|
47
|
+
raise 'At least one prompt is required' if form_field.prompts.empty?
|
48
|
+
|
49
|
+
if self.class == VoiceForm::Form
|
50
|
+
self.form_stack << form_field
|
51
|
+
else
|
52
|
+
unless self.respond_to?(field_name)
|
53
|
+
self.class.class_eval do
|
54
|
+
attr_accessor field_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
form_field.run
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe VoiceForm::FormField do
|
4
|
+
include VoiceForm::FormMethods
|
5
|
+
|
6
|
+
attr_accessor :call_context, :my_field
|
7
|
+
|
8
|
+
before do
|
9
|
+
@call_context = mock('CallContext', :play => nil, :speak => nil)
|
10
|
+
@call_context.stub!(:input).with(any_args).and_return('')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should define accessor for field in component" do
|
14
|
+
field(:my_field) do
|
15
|
+
prompt :speak => 'test'
|
16
|
+
end
|
17
|
+
self.methods.include?(:my_field)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise error if no prompts defined" do
|
21
|
+
item = form_field(:my_field) do
|
22
|
+
end
|
23
|
+
lambda { item.run }.should raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise error if reprompt defined before prompt" do
|
27
|
+
lambda {
|
28
|
+
form_field(:my_field) do
|
29
|
+
reprompt :speak => 'and again'
|
30
|
+
end
|
31
|
+
}.should raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return same prompt for for all attempts if single prompt" do
|
35
|
+
item = form_field(:my_field) do
|
36
|
+
prompt :speak => "first"
|
37
|
+
end
|
38
|
+
item.send(:prompt_for_attempt, 1)[:speak].should == 'first'
|
39
|
+
item.send(:prompt_for_attempt, 2)[:speak].should == 'first'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return reprompt for subsequent prompts" do
|
43
|
+
item = form_field(:my_field) do
|
44
|
+
prompt :speak => "first"
|
45
|
+
reprompt :speak => 'next'
|
46
|
+
end
|
47
|
+
item.send(:prompt_for_attempt, 1)[:speak].should == 'first'
|
48
|
+
item.send(:prompt_for_attempt, 2)[:speak].should == 'next'
|
49
|
+
item.send(:prompt_for_attempt, 3)[:speak].should == 'next'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return prompt for given number of repeats before subsequent prompts" do
|
53
|
+
item = form_field(:my_field) do
|
54
|
+
prompt :speak => "first", :repeats => 2
|
55
|
+
reprompt :speak => 'next'
|
56
|
+
end
|
57
|
+
item.send(:prompt_for_attempt, 1)[:speak].should == 'first'
|
58
|
+
item.send(:prompt_for_attempt, 2)[:speak].should == 'first'
|
59
|
+
item.send(:prompt_for_attempt, 3)[:speak].should == 'next'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should set input value in component" do
|
63
|
+
item = form_field(:my_field, :length => 3) do
|
64
|
+
prompt :speak => "first"
|
65
|
+
end
|
66
|
+
call_context.stub!(:input).and_return('123')
|
67
|
+
item.run
|
68
|
+
|
69
|
+
my_field.should == '123'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should run setup callback once" do
|
73
|
+
call_me = i_should_be_called
|
74
|
+
item = form_field(:my_field, :attempts => 3) do
|
75
|
+
prompt :speak => "first"
|
76
|
+
setup { call_me.call }
|
77
|
+
end
|
78
|
+
call_context.should_receive(:input).and_return('')
|
79
|
+
|
80
|
+
item.run
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should run timeout callback if no input" do
|
84
|
+
call_me = i_should_be_called
|
85
|
+
item = form_field(:my_field, :attempts => 1) do
|
86
|
+
prompt :speak => "first"
|
87
|
+
timeout { call_me.call }
|
88
|
+
end
|
89
|
+
call_context.should_receive(:input).and_return('')
|
90
|
+
|
91
|
+
item.run
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should make all attempts to get valid input" do
|
95
|
+
item = form_field(:my_field) do
|
96
|
+
prompt :speak => "first"
|
97
|
+
end
|
98
|
+
call_context.should_receive(:input).exactly(3).times
|
99
|
+
|
100
|
+
item.run
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should make one attempt if input is valid" do
|
104
|
+
item = form_field(:my_field) do
|
105
|
+
prompt :speak => "first"
|
106
|
+
end
|
107
|
+
item.stub!(:input_valid?).and_return(true)
|
108
|
+
call_context.should_receive(:input).once
|
109
|
+
|
110
|
+
item.run
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should check if input_valid?" do
|
114
|
+
item = form_field(:my_field, :length => 3) do
|
115
|
+
prompt :speak => "first"
|
116
|
+
end
|
117
|
+
call_context.should_receive(:input).and_return('123')
|
118
|
+
item.should_receive(:input_valid?)
|
119
|
+
|
120
|
+
item.run
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should run validation callback if defined" do
|
124
|
+
call_me = i_should_be_called
|
125
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
126
|
+
prompt :speak => "first"
|
127
|
+
validate { call_me.call }
|
128
|
+
end
|
129
|
+
call_context.stub!(:input).and_return('123')
|
130
|
+
|
131
|
+
item.run
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should run confirm callback if defined" do
|
135
|
+
call_me = i_should_be_called
|
136
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
137
|
+
prompt :speak => "first"
|
138
|
+
confirm { call_me.call; [] }
|
139
|
+
end
|
140
|
+
call_context.stub!(:input).and_return('123')
|
141
|
+
|
142
|
+
item.run
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "confirm callback" do
|
146
|
+
|
147
|
+
it "should not run if not valid input" do
|
148
|
+
dont_call_me = i_should_not_be_called
|
149
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
150
|
+
prompt :speak => "first"
|
151
|
+
validate { false }
|
152
|
+
confirm { dont_call_me.call }
|
153
|
+
end
|
154
|
+
call_context.stub!(:input).and_return('123')
|
155
|
+
|
156
|
+
item.run
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should be run the number of attempts if no valid response" do
|
160
|
+
call_context.should_receive(:input).with(3, anything).and_return('123')
|
161
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
162
|
+
prompt :speak => "first"
|
163
|
+
|
164
|
+
confirm(:attempts => 3) { [] }
|
165
|
+
end
|
166
|
+
call_context.should_receive(:input).with(1, anything).exactly(3).times.and_return('')
|
167
|
+
|
168
|
+
item.run
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should run success callback if accept value entered" do
|
172
|
+
call_me = i_should_be_called
|
173
|
+
call_context.should_receive(:input).with(3, anything).and_return('123')
|
174
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
175
|
+
prompt :speak => "first"
|
176
|
+
|
177
|
+
success { call_me.call }
|
178
|
+
confirm(:accept => '1', :reject => '2') { [] }
|
179
|
+
end
|
180
|
+
call_context.should_receive(:input).with(1, anything).and_return('1')
|
181
|
+
|
182
|
+
item.run
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should run failure callback if reject value entered" do
|
186
|
+
call_me = i_should_be_called
|
187
|
+
call_context.should_receive(:input).with(3, anything).and_return('123')
|
188
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
189
|
+
prompt :speak => "first"
|
190
|
+
|
191
|
+
failure { call_me.call }
|
192
|
+
confirm(:accept => '1', :reject => '2') { [] }
|
193
|
+
end
|
194
|
+
call_context.should_receive(:input).with(1, anything).and_return('2')
|
195
|
+
|
196
|
+
item.run
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should play confirmation input prompt if confirm block return value is array" do
|
200
|
+
call_me = i_should_be_called
|
201
|
+
call_context.should_receive(:input).with(3, anything).and_return('123')
|
202
|
+
|
203
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
204
|
+
prompt :speak => "first"
|
205
|
+
|
206
|
+
success { call_me.call }
|
207
|
+
confirm(:timeout => 3) { [] }
|
208
|
+
end
|
209
|
+
call_context.should_receive(:input).with(1, {:play => [], :timeout => 3}).and_return('1')
|
210
|
+
|
211
|
+
item.run
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should speak confirmation input prompt if confirm block return value is string" do
|
215
|
+
call_me = i_should_be_called
|
216
|
+
call_context.should_receive(:input).with(3, anything).and_return('123')
|
217
|
+
|
218
|
+
item = form_field(:my_field, :length => 3, :attempts => 1) do
|
219
|
+
prompt :speak => "first"
|
220
|
+
|
221
|
+
success { call_me.call }
|
222
|
+
confirm(:timeout => 3) { '' }
|
223
|
+
end
|
224
|
+
call_context.should_receive(:input).with(1, {:speak => '', :timeout => 3}).and_return('1')
|
225
|
+
|
226
|
+
item.run
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should run failure callback if no input" do
|
231
|
+
call_me = i_should_be_called
|
232
|
+
item = form_field(:my_field, :length => 3) do
|
233
|
+
prompt :speak => "first"
|
234
|
+
failure { call_me.call }
|
235
|
+
end
|
236
|
+
call_context.should_receive(:input).and_return('')
|
237
|
+
|
238
|
+
item.run
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should run success callback if input valid length" do
|
242
|
+
call_me = i_should_be_called
|
243
|
+
item = form_field(:my_field, :length => 3) do
|
244
|
+
prompt :speak => "first"
|
245
|
+
success { call_me.call }
|
246
|
+
end
|
247
|
+
call_context.should_receive(:input).and_return('123')
|
248
|
+
|
249
|
+
item.run
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should run success callback if input valid length and valid input" do
|
253
|
+
validate_me = i_should_be_called
|
254
|
+
call_me = i_should_be_called
|
255
|
+
item = form_field(:my_field, :length => 3) do
|
256
|
+
prompt :speak => "first"
|
257
|
+
validate do
|
258
|
+
validate_me.call
|
259
|
+
my_field.to_i > 100
|
260
|
+
end
|
261
|
+
success { call_me.call }
|
262
|
+
end
|
263
|
+
call_context.should_receive(:input).and_return('123')
|
264
|
+
|
265
|
+
item.run
|
266
|
+
end
|
267
|
+
|
268
|
+
def form_field(field, options={}, &block)
|
269
|
+
self.class.class_eval { attr_accessor field }
|
270
|
+
item = VoiceForm::FormField.new(field, {:attempts => 3}.merge(options), self )
|
271
|
+
|
272
|
+
item.instance_eval(&block)
|
273
|
+
item
|
274
|
+
end
|
275
|
+
end
|
data/spec/form_spec.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe VoiceForm::Form do
|
4
|
+
include VoiceForm
|
5
|
+
|
6
|
+
attr_accessor :call_context
|
7
|
+
|
8
|
+
before do
|
9
|
+
new_voice_form
|
10
|
+
@call_context = mock('CallContext', :play => nil, :speak => nil)
|
11
|
+
@call_context.stub!(:input).with(any_args).and_return('')
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should define form and run it" do
|
15
|
+
call_me = i_should_be_called
|
16
|
+
|
17
|
+
self.class.voice_form &call_me
|
18
|
+
|
19
|
+
start_voice_form
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should call setup block" do
|
23
|
+
form.setup &i_should_be_called
|
24
|
+
|
25
|
+
run_form
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should run single form field" do
|
29
|
+
call_me = i_should_be_called
|
30
|
+
|
31
|
+
form.field(:my_field) do
|
32
|
+
prompt :speak => 'enter value'
|
33
|
+
setup { call_me.call }
|
34
|
+
end
|
35
|
+
|
36
|
+
run_form
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should run all form fields" do
|
40
|
+
first_call_me = i_should_be_called
|
41
|
+
second_call_me = i_should_be_called
|
42
|
+
|
43
|
+
form.field(:first_field) do
|
44
|
+
prompt :speak => 'enter value'
|
45
|
+
setup { first_call_me.call }
|
46
|
+
end
|
47
|
+
form.field(:second_field) do
|
48
|
+
prompt :speak => 'enter value'
|
49
|
+
setup { second_call_me.call }
|
50
|
+
end
|
51
|
+
|
52
|
+
run_form
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should run do_blocks" do
|
56
|
+
do_block_call_me = i_should_be_called
|
57
|
+
|
58
|
+
form.do_block { do_block_call_me.call }
|
59
|
+
|
60
|
+
run_form
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should run all fields and do_blocks" do
|
64
|
+
field_call_me = i_should_be_called
|
65
|
+
do_block_call_me = i_should_be_called
|
66
|
+
|
67
|
+
form.field(:first_field) do
|
68
|
+
prompt :speak => 'enter value'
|
69
|
+
setup { field_call_me.call }
|
70
|
+
end
|
71
|
+
form.do_block { do_block_call_me.call }
|
72
|
+
|
73
|
+
run_form
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should jump forward in form stack to field in goto" do
|
77
|
+
first_call_me = i_should_be_called
|
78
|
+
do_block_call_me = i_should_not_be_called
|
79
|
+
second_call_me = i_should_be_called
|
80
|
+
|
81
|
+
form.field(:first_field, :attempts => 1) do
|
82
|
+
prompt :speak => 'enter value'
|
83
|
+
setup { first_call_me.call }
|
84
|
+
failure { form.goto :second_field }
|
85
|
+
end
|
86
|
+
|
87
|
+
form.do_block { do_block_call_me.call }
|
88
|
+
|
89
|
+
form.field(:second_field) do
|
90
|
+
prompt :speak => 'enter value'
|
91
|
+
setup { second_call_me.call }
|
92
|
+
end
|
93
|
+
|
94
|
+
run_form
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should jump back in form stack to goto field and repeat form stack items" do
|
98
|
+
first_call_me = i_should_be_called(2)
|
99
|
+
do_block_call_me = i_should_be_called(2)
|
100
|
+
second_call_me = i_should_be_called(2)
|
101
|
+
|
102
|
+
form.field(:first_field, :attempts => 1) do
|
103
|
+
prompt :speak => 'enter value'
|
104
|
+
setup { first_call_me.call }
|
105
|
+
end
|
106
|
+
|
107
|
+
form.do_block { do_block_call_me.call }
|
108
|
+
|
109
|
+
form.field(:second_field) do
|
110
|
+
prompt :speak => 'enter value'
|
111
|
+
setup { second_call_me.call }
|
112
|
+
failure {
|
113
|
+
unless @once
|
114
|
+
@once = true
|
115
|
+
form.goto :first_field
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
run_form
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should restart form and repeat all form stack items" do
|
124
|
+
first_call_me = i_should_be_called(2)
|
125
|
+
do_block_call_me = i_should_be_called(2)
|
126
|
+
second_call_me = i_should_be_called(2)
|
127
|
+
|
128
|
+
form.field(:first_field, :attempts => 1) do
|
129
|
+
prompt :speak => 'enter value'
|
130
|
+
setup { first_call_me.call }
|
131
|
+
end
|
132
|
+
|
133
|
+
form.do_block { do_block_call_me.call }
|
134
|
+
|
135
|
+
form.field(:second_field) do
|
136
|
+
prompt :speak => 'enter value'
|
137
|
+
setup { second_call_me.call }
|
138
|
+
failure {
|
139
|
+
unless @once
|
140
|
+
@once = true
|
141
|
+
form.restart
|
142
|
+
end
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
run_form
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should exit form and not run subsequent fields" do
|
150
|
+
first_call_me = i_should_be_called
|
151
|
+
do_block_call_me = i_should_not_be_called
|
152
|
+
second_call_me = i_should_not_be_called
|
153
|
+
|
154
|
+
form.field(:first_field, :attempts => 1) do
|
155
|
+
prompt :speak => 'enter value'
|
156
|
+
setup { first_call_me.call }
|
157
|
+
failure { form.exit }
|
158
|
+
end
|
159
|
+
|
160
|
+
form.do_block { do_block_call_me.call }
|
161
|
+
|
162
|
+
form.field(:second_field) do
|
163
|
+
prompt :speak => 'enter value'
|
164
|
+
setup { second_call_me.call }
|
165
|
+
end
|
166
|
+
|
167
|
+
run_form
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "current_field" do
|
171
|
+
before do
|
172
|
+
@field = nil
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should be name of current field being run" do
|
176
|
+
form.field(:my_field) do
|
177
|
+
prompt :speak => 'enter value'
|
178
|
+
setup { @field = form.current_field }
|
179
|
+
end
|
180
|
+
run_form
|
181
|
+
|
182
|
+
@field.should == :my_field
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should be nil in do_block" do
|
186
|
+
form.do_block do
|
187
|
+
@field = form.current_field
|
188
|
+
end
|
189
|
+
run_form
|
190
|
+
|
191
|
+
@field.should be_nil
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should be nil after form is run" do
|
195
|
+
form.field(:my_field) do
|
196
|
+
prompt :speak => 'enter value'
|
197
|
+
end
|
198
|
+
run_form
|
199
|
+
|
200
|
+
form.current_field.should be_nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def new_voice_form
|
205
|
+
self.class.voice_form { }
|
206
|
+
self.form = VoiceForm::Form.new(self.class.voice_form_options[0])
|
207
|
+
self.form.instance_eval(&self.class.voice_form_options[1])
|
208
|
+
end
|
209
|
+
|
210
|
+
def run_form
|
211
|
+
form.run(self)
|
212
|
+
end
|
213
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
2
|
+
$: << File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'spec'
|
6
|
+
require 'active_support'
|
7
|
+
|
8
|
+
require 'voice_form/form_methods'
|
9
|
+
require 'voice_form/form'
|
10
|
+
require 'voice_form/form_field'
|
11
|
+
|
12
|
+
module SpecHelpers
|
13
|
+
def i_should_be_called(times=1, &block)
|
14
|
+
proc = mock('Proc should be called')
|
15
|
+
proc.should_receive(:call).exactly(times).times.instance_eval(&(block || Proc.new {}))
|
16
|
+
Proc.new { |*args| proc.call(*args) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def i_should_not_be_called(&block)
|
20
|
+
proc = mock('Proc should be called')
|
21
|
+
proc.should_not_receive(:call).instance_eval(&(block || Proc.new {}))
|
22
|
+
Proc.new { |*args| proc.call(*args) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Runner.configure do |config|
|
27
|
+
config.include SpecHelpers
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adzap-voice_form
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Meehan
|
8
|
+
autorequire: voice_form
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-11 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A DSL for Adhearsion to create forms in the style of the VoiceXML form element.
|
17
|
+
email: adam.meehan@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- History.txt
|
24
|
+
files:
|
25
|
+
- MIT-LICENSE
|
26
|
+
- README.markdown
|
27
|
+
- Rakefile
|
28
|
+
- lib/voice_form.rb
|
29
|
+
- lib/voice_form
|
30
|
+
- lib/voice_form/form.rb
|
31
|
+
- lib/voice_form/form_field.rb
|
32
|
+
- lib/voice_form/form_methods.rb
|
33
|
+
- spec/form_field_spec.rb
|
34
|
+
- spec/spec_helper.rb
|
35
|
+
- spec/form_spec.rb
|
36
|
+
- examples/my_component.rb
|
37
|
+
- History.txt
|
38
|
+
has_rdoc: false
|
39
|
+
homepage: http://github.com/adzap/voice_form
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: A DSL for Adhearsion to create forms in the style of the VoiceXML form element.
|
64
|
+
test_files: []
|
65
|
+
|