larynx 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/bin/larynx CHANGED
@@ -5,3 +5,4 @@ unless defined?(Gem)
5
5
  end
6
6
 
7
7
  require 'larynx'
8
+ Larynx::Server.boot
@@ -1,11 +1,10 @@
1
1
  require 'rubygems'
2
2
  require 'eventmachine'
3
3
  require 'active_support'
4
- require 'logger'
5
- require 'daemons/daemonize'
6
4
 
7
5
  require 'larynx/version'
8
6
  require 'larynx/logger'
7
+ require 'larynx/server'
9
8
  require 'larynx/observable'
10
9
  require 'larynx/callbacks'
11
10
  require 'larynx/callbacks_with_async'
@@ -15,7 +14,7 @@ require 'larynx/command'
15
14
  require 'larynx/commands'
16
15
  require 'larynx/prompt'
17
16
  require 'larynx/application'
18
- require 'larynx/fields'
17
+ require 'larynx/field'
19
18
  require 'larynx/form'
20
19
  require 'larynx/restartable_timer'
21
20
  require 'larynx/call_handler'
@@ -23,97 +22,9 @@ require 'larynx/call_handler'
23
22
  module Larynx
24
23
  class << self
25
24
  include Callbacks
26
-
27
25
  define_callback :connect, :answer, :hungup
28
-
29
- def parse_options(args=ARGV)
30
- @options = {
31
- :ip => "0.0.0.0",
32
- :port => 8084,
33
- :pid_file => "./larynx.pid",
34
- :log_file => "./larynx.log"
35
- }
36
- opts = OptionParser.new
37
- opts.banner = "Usage: larynx [options]"
38
- opts.separator ''
39
- opts.separator "Larynx is a framework to develop FreeSWITCH IVR applications in Ruby."
40
- opts.on('-i', '--ip IP', 'Listen for connections on this IP') {|ip| @options[:ip] = ip }
41
- opts.on('-p', '--port PORT', 'Listen on this port', Integer) {|port| @options[:port] = port }
42
- opts.on('-d', '--daemonize', 'Run as daemon') { @options[:daemonize] = true }
43
- opts.on('-l', '--log-file FILE', 'Defaults to /app/root/larynx.log') {|log| @options[:log_file] = log }
44
- opts.on( '--pid-file FILE', 'Defaults to /app/root/larynx.pid') {|pid| @options[:pid_file] = pid }
45
- opts.on('-h', '--help', 'This is it') { $stderr.puts opts; exit 0 }
46
- opts.on('-v', '--version') { $stderr.puts "Larynx version #{Larynx::VERSION}"; exit 0 }
47
- opts.parse!(args)
48
- end
49
-
50
- def setup_logger
51
- logger = Larynx::Logger.new(@options[:log_file])
52
- logger.level = Logger::INFO
53
- Object.const_set "LARYNX_LOGGER", logger
54
- end
55
-
56
- def graceful_exit
57
- msg = "Shutting down Larynx"
58
- $stderr.puts msg unless @options[:daemon]
59
- LARYNX_LOGGER.info msg
60
-
61
- EM.stop_server @em_signature
62
- @em_signature = nil
63
- remove_pid_file if @options[:daemonize]
64
- exit 130
65
- end
66
-
67
- def daemonize
68
- Daemonize.daemonize
69
- Dir.chdir LARYNX_ROOT
70
- File.open(@options[:pid_file], 'w+') {|f| f.write("#{Process.pid}\n") }
71
- end
72
-
73
- def remove_pid_file
74
- File.delete @options[:pid_file]
75
- end
76
-
77
- def trap_signals
78
- trap('TERM') { graceful_exit }
79
- trap('INT') { graceful_exit }
80
- end
81
-
82
- def setup_app
83
- if ARGV[0].nil?
84
- $stderr.puts "You must specify an application file"
85
- exit -1
86
- end
87
- Object.const_set "LARYNX_ROOT", File.expand_path(File.dirname(ARGV[0]))
88
- require File.expand_path(ARGV[0])
89
- end
90
-
91
- def start_server
92
- msg = "Larynx starting up on #{@options[:ip]}:#{@options[:port]}"
93
- $stderr.puts msg unless @options[:daemon]
94
- LARYNX_LOGGER.info msg
95
-
96
- EM::run {
97
- @em_signature = EM::start_server @options[:ip], @options[:port], Larynx::CallHandler
98
- }
99
- end
100
-
101
- def run
102
- parse_options(ARGV)
103
- setup_app
104
- daemonize if @options[:daemonize]
105
- setup_logger
106
- trap_signals
107
- start_server
108
- end
109
-
110
- def running?
111
- !@em_signature.nil?
112
- end
113
26
  end
114
27
 
115
28
  # Default connect callback is to answer call
116
29
  connect {|call| call.answer }
117
30
  end
118
-
119
- Larynx.run unless defined?(TEST)
@@ -0,0 +1,122 @@
1
+ module Larynx
2
+ class NoPromptDefined < StandardError; end
3
+
4
+ class Field
5
+ include CallbacksWithAsync
6
+
7
+ VALID_PROMPT_OPTIONS = [:play, :speak, :phrase, :bargein, :repeats, :interdigit_timeout, :timeout]
8
+
9
+ attr_reader :name, :app, :attempt
10
+ define_callback :setup, :validate, :invalid, :success, :failure, :scope => :app
11
+
12
+ def initialize(name, options, &block)
13
+ @name = name
14
+ @options = options.reverse_merge(:attempts => 3)
15
+ @prompt_queue = []
16
+
17
+ instance_eval(&block)
18
+ raise(Larynx::NoPromptDefined, 'A field requires a prompt to be defined') if @prompt_queue.empty?
19
+ end
20
+
21
+ def prompt(options)
22
+ add_prompt(options)
23
+ end
24
+
25
+ def reprompt(options)
26
+ raise 'A reprompt can only be used after a prompt' if @prompt_queue.empty?
27
+ add_prompt(options)
28
+ end
29
+
30
+ def add_prompt(options)
31
+ options.assert_valid_keys(*VALID_PROMPT_OPTIONS)
32
+ repeats = options.delete(:repeats) || 1
33
+ options.merge!(@options.slice(:length, :min_length, :max_length, :interdigit_timeout, :timeout))
34
+ @prompt_queue += ([options] * repeats)
35
+ end
36
+
37
+ def current_prompt
38
+ options = (@prompt_queue[@attempt-1] || @prompt_queue.last).dup
39
+ method = command_from_options(options)
40
+ message = options[method].is_a?(Symbol) ? @app.send(options[method]) : options[method]
41
+ options[method] = message
42
+
43
+ Prompt.new(call, options) {|input, result|
44
+ set_instance_variables(input, result)
45
+ evaluate_input
46
+ }
47
+ end
48
+
49
+ def execute_prompt
50
+ call.execute current_prompt.command
51
+ send_next_command
52
+ end
53
+
54
+ def increment_attempts
55
+ @attempt += 1
56
+ end
57
+
58
+ # hook called when callback is complete
59
+ def callback_complete(callback, result=true)
60
+ case callback
61
+ when :validate
62
+ evaluate_validity(result)
63
+ when :invalid
64
+ invalid_input
65
+ when :success, :failure
66
+ finalize
67
+ end
68
+ end
69
+
70
+ def evaluate_input
71
+ @valid_length ? fire_callback(:validate) : fire_callback(:invalid)
72
+ end
73
+
74
+ def evaluate_validity(result)
75
+ result ? fire_callback(:success) : fire_callback(:invalid)
76
+ end
77
+
78
+ def invalid_input
79
+ if last_attempt?
80
+ fire_callback(:failure)
81
+ else
82
+ increment_attempts
83
+ execute_prompt
84
+ end
85
+ end
86
+
87
+ def send_next_command
88
+ call.send_next_command if call.state == :ready
89
+ end
90
+
91
+ def set_instance_variables(input, result)
92
+ @value, @valid_length = input, result
93
+ @app.send("#{@name}=", input)
94
+ end
95
+
96
+ def command_from_options(options)
97
+ (Prompt::COMMAND_OPTIONS & options.keys).first
98
+ end
99
+
100
+ def run(app)
101
+ @app = app
102
+ @attempt = 1
103
+ call.add_observer self
104
+ fire_callback(:setup)
105
+ execute_prompt
106
+ end
107
+
108
+ def call
109
+ @app.call
110
+ end
111
+
112
+ def finalize
113
+ call.remove_observer self
114
+ send_next_command
115
+ end
116
+
117
+ def last_attempt?
118
+ @attempt == @options[:attempts]
119
+ end
120
+
121
+ end
122
+ end
@@ -1,10 +1,27 @@
1
1
  module Larynx
2
2
  class Form < Application
3
- include Fields
4
3
  class_inheritable_accessor :setup_block
5
4
 
6
- def self.setup(&block)
7
- self.setup_block = block
5
+ class_inheritable_accessor :field_definitions
6
+ self.field_definitions = []
7
+
8
+ attr_accessor :fields
9
+
10
+ class << self
11
+ def setup(&block)
12
+ self.setup_block = block
13
+ end
14
+
15
+ def field(name, options={}, &block)
16
+ self.field_definitions << {:name => name, :options => options, :block => block}
17
+ attr_accessor name
18
+ end
19
+ end
20
+
21
+ def initialize(*args, &block)
22
+ @fields = self.class.field_definitions.map {|field| Field.new(field[:name], field[:options], &field[:block]) }
23
+ @field_index = -1
24
+ super
8
25
  end
9
26
 
10
27
  def run
@@ -16,5 +33,34 @@ module Larynx
16
33
  @field_index = -1
17
34
  run
18
35
  end
36
+
37
+ def next_field(field_name=nil)
38
+ if field_name
39
+ @field_index = field_index(field_name)
40
+ else
41
+ @field_index += 1
42
+ end
43
+ if field = current_field
44
+ field.run(self)
45
+ field
46
+ end
47
+ end
48
+
49
+ def current_field
50
+ @fields[@field_index]
51
+ end
52
+
53
+ def field_index(name)
54
+ field = @fields.find {|f| f.name == name }
55
+ @fields.index(field)
56
+ end
57
+
58
+ def attempt
59
+ current_field.attempt
60
+ end
61
+
62
+ def last_attempt?
63
+ current_field.last_attempt?
64
+ end
19
65
  end
20
66
  end
@@ -0,0 +1,4 @@
1
+ module Larynx
2
+ class Menu
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module Larynx
2
- VERSION = '0.1.5'
2
+ VERSION = '0.1.6'
3
3
  end
@@ -0,0 +1,218 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ class TestForm < Larynx::Form; end
4
+
5
+ describe Larynx::Field do
6
+ attr_reader :call, :form
7
+
8
+ before do
9
+ @call = TestCallHandler.new(1)
10
+ @form = define_form.new(@call)
11
+ end
12
+
13
+ it 'should raise exception if field has no prompt' do
14
+ lambda { field(:guess) {} }.should raise_exception(Larynx::NoPromptDefined)
15
+ end
16
+
17
+ it 'should run setup callback once' do
18
+ call_me = should_be_called
19
+ fld = field(:guess) do
20
+ prompt :speak => 'first'
21
+ setup &call_me
22
+ end
23
+ fld.run form
24
+ end
25
+
26
+ it 'should pass timeout and length options to the prompt object' do
27
+ fld = field(:guess, :length => 1, :min_length => 1, :max_length => 2, :interdigit_timeout => 1, :timeout => 2) do
28
+ prompt :speak => 'first'
29
+ end
30
+ fld.run(form)
31
+ prompt = fld.current_prompt
32
+ prompt.interdigit_timeout.should == 1
33
+ prompt.timeout.should == 2
34
+ prompt.minimum_length.should == 1
35
+ prompt.maximum_length.should == 2
36
+ end
37
+
38
+ it 'should return same prompt all attempts if single prompt' do
39
+ fld = field(:guess) do
40
+ prompt :speak => 'first'
41
+ end
42
+ fld.run(form)
43
+ fld.current_prompt.message.should == 'first'
44
+ fld.increment_attempts
45
+ fld.current_prompt.message.should == 'first'
46
+ end
47
+
48
+ it 'should return reprompt for subsequent prompts' do
49
+ fld = field(:guess) do
50
+ prompt :speak => 'first'
51
+ reprompt :speak => 'second'
52
+ end
53
+ fld.run(form)
54
+ fld.current_prompt.message.should == 'first'
55
+ fld.increment_attempts
56
+ fld.current_prompt.message.should == 'second'
57
+ end
58
+
59
+ it 'should return prompt for given number of repeats before subsequent prompts' do
60
+ fld = field(:guess) do
61
+ prompt :speak => 'first', :repeats => 2
62
+ reprompt :speak => 'second'
63
+ end
64
+ fld.run(form)
65
+ fld.current_prompt.message.should == 'first'
66
+ fld.increment_attempts
67
+ fld.current_prompt.message.should == 'first'
68
+ fld.increment_attempts
69
+ fld.current_prompt.message.should == 'second'
70
+ end
71
+
72
+ context "#last_attempt?" do
73
+ it 'should return false when current attempt not equal to max attempts' do
74
+ fld = field(:guess, :attempts => 2) do
75
+ prompt :speak => 'first'
76
+ end
77
+ fld.run(form)
78
+ fld.attempt.should == 1
79
+ fld.last_attempt?.should be_false
80
+ end
81
+
82
+ it 'should return true when current attempt equals max attempts' do
83
+ fld = field(:guess, :attempts => 2) do
84
+ prompt :speak => 'first'
85
+ end
86
+ fld.run(form)
87
+ fld.increment_attempts
88
+ fld.attempt.should == 2
89
+ fld.last_attempt?.should be_true
90
+ end
91
+ end
92
+
93
+ context 'input evaluation' do
94
+ it 'should run validate callback if input minimum length' do
95
+ call_me = should_be_called
96
+ fld = field(:guess, :min_length => 1) do
97
+ prompt :speak => 'first'
98
+ validate &call_me
99
+ end
100
+ fld.run form
101
+ call.input << '1'
102
+ fld.current_prompt.finalise
103
+ end
104
+
105
+ it 'should run invalid callback if length not valid' do
106
+ call_me = should_be_called
107
+ fld = field(:guess) do
108
+ prompt :speak => 'first'
109
+ invalid &call_me
110
+ end
111
+ fld.run form
112
+ fld.current_prompt.finalise
113
+ end
114
+
115
+ it 'should run invalid callback if validate callback returns false' do
116
+ call_me = should_be_called
117
+ fld = field(:guess, :min_length => 1) do
118
+ prompt :speak => 'first'
119
+ validate { false }
120
+ invalid &call_me
121
+ end
122
+ fld.run form
123
+ call.input << '1'
124
+ fld.current_prompt.finalise
125
+ end
126
+
127
+ it 'should run invalid callback if validate callback returns nil' do
128
+ call_me = should_be_called
129
+ fld = field(:guess, :min_length => 1) do
130
+ prompt :speak => 'first'
131
+ validate { nil }
132
+ invalid &call_me
133
+ end
134
+ fld.run form
135
+ call.input << '1'
136
+ fld.current_prompt.finalise
137
+ end
138
+
139
+ it 'should run success callback if length valid and no validate callback' do
140
+ call_me = should_be_called
141
+ fld = field(:guess, :min_length => 1) do
142
+ prompt :speak => 'first'
143
+ success &call_me
144
+ end
145
+ fld.run form
146
+ call.input << '1'
147
+ fld.current_prompt.finalise
148
+ end
149
+
150
+ it 'should run success callback if validate callback returns true' do
151
+ call_me = should_be_called
152
+ fld = field(:guess, :min_length => 1) do
153
+ prompt :speak => 'first'
154
+ validate { true }
155
+ success &call_me
156
+ end
157
+ fld.run form
158
+ call.input << '1'
159
+ fld.current_prompt.finalise
160
+ end
161
+
162
+ it 'should run failure callback if not valid and last attempt' do
163
+ call_me = should_be_called
164
+ fld = field(:guess, :min_length => 1, :attempts => 1) do
165
+ prompt :speak => 'first'
166
+ failure &call_me
167
+ end
168
+ fld.run form
169
+ fld.current_prompt.finalise
170
+ end
171
+
172
+ it 'should increment attempts if not valid' do
173
+ fld = field(:guess) do
174
+ prompt :speak => 'first'
175
+ reprompt :speak => 'second'
176
+ end
177
+ fld.run form
178
+ fld.current_prompt.finalise
179
+ fld.current_prompt.message.should == 'second'
180
+ end
181
+
182
+ it 'should execute next prompt if not valid' do
183
+ fld = field(:guess) do
184
+ prompt :speak => 'first'
185
+ reprompt :speak => 'second'
186
+ end
187
+ fld.run form
188
+ fld.should_receive(:execute_prompt)
189
+ fld.current_prompt.finalise
190
+ end
191
+
192
+ context "async callbacks" do
193
+ # it "should be run in thread" do
194
+ # em do
195
+ # fld = field(:guess) do
196
+ # prompt :speak => 'first'
197
+ # validate(:async) { sleep(0.25) }
198
+ # success { done }
199
+ # end.run(form)
200
+ # call.input << '1'
201
+ # end
202
+ # @callback.should be_nil
203
+ # end
204
+ end
205
+
206
+ end
207
+
208
+ def field(name, options={}, &block)
209
+ @form.class.class_eval { attr_accessor name }
210
+ Larynx::Field.new(name, options, &block)
211
+ end
212
+
213
+ def define_form(&block)
214
+ reset_class(TestForm) do
215
+ instance_eval &block if block_given?
216
+ end
217
+ end
218
+ end
@@ -9,6 +9,16 @@ describe Larynx::Form do
9
9
  @call = TestCallHandler.new(1)
10
10
  end
11
11
 
12
+ it 'should add field class method' do
13
+ Larynx::Form.should respond_to(:field)
14
+ end
15
+
16
+ it 'should add instance accessor for field name' do
17
+ form_class = define_form
18
+ form_class.field(:guess) { prompt :speak => 'hello' }
19
+ form_class.methods.include?(:guess)
20
+ end
21
+
12
22
  context "#run" do
13
23
  it 'should call setup block' do
14
24
  this_should_be_called = should_be_called
@@ -40,6 +50,40 @@ describe Larynx::Form do
40
50
  end
41
51
  end
42
52
 
53
+ context '#next_field' do
54
+ let(:form) {
55
+ define_form do
56
+ field(:field1) { prompt :speak => 'hello' }
57
+ field(:field2) { prompt :speak => 'hello' }
58
+ field(:field3) { prompt :speak => 'hello' }
59
+ end.new(call)
60
+ }
61
+
62
+ it 'should iterate over defined fields' do
63
+ form.next_field.name.should == :field1
64
+ form.next_field.name.should == :field2
65
+ form.next_field.name.should == :field3
66
+ end
67
+
68
+ it 'should jump to field name if supplied' do
69
+ form.next_field(:field2).name.should == :field2
70
+ end
71
+ end
72
+
73
+ context "#current_field" do
74
+ it 'should return field of current position' do
75
+ form = define_form do
76
+ field(:field1) { prompt :speak => 'hello' }
77
+ field(:field2) { prompt :speak => 'hello' }
78
+ end.new(call)
79
+ form.run
80
+
81
+ form.current_field.should == form.fields[0]
82
+ form.next_field
83
+ form.current_field.should == form.fields[1]
84
+ end
85
+ end
86
+
43
87
  def define_form(&block)
44
88
  reset_class(TestForm) do
45
89
  instance_eval &block if block_given?
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Larynx::Menu do
4
+
5
+ it 'should' do
6
+ end
7
+
8
+ def define_menu(&block)
9
+ Class.new(Larynx::Menu, &block)
10
+ end
11
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: larynx
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 5
10
- version: 0.1.5
9
+ - 6
10
+ version: 0.1.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam Meehan
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-03 00:00:00 +10:00
18
+ date: 2010-09-04 00:00:00 +10:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -100,9 +100,10 @@ files:
100
100
  - lib/larynx/callbacks_with_async.rb
101
101
  - lib/larynx/command.rb
102
102
  - lib/larynx/commands.rb
103
- - lib/larynx/fields.rb
103
+ - lib/larynx/field.rb
104
104
  - lib/larynx/form.rb
105
105
  - lib/larynx/logger.rb
106
+ - lib/larynx/menu.rb
106
107
  - lib/larynx/observable.rb
107
108
  - lib/larynx/prompt.rb
108
109
  - lib/larynx/response.rb
@@ -122,8 +123,9 @@ files:
122
123
  - spec/larynx/call_handler_spec.rb
123
124
  - spec/larynx/command_spec.rb
124
125
  - spec/larynx/eventmachince_spec.rb
125
- - spec/larynx/fields_spec.rb
126
+ - spec/larynx/field_spec.rb
126
127
  - spec/larynx/form_spec.rb
128
+ - spec/larynx/menu_spec.rb
127
129
  - spec/larynx/prompt_spec.rb
128
130
  - spec/larynx_spec.rb
129
131
  - spec/spec_helper.rb
@@ -1,184 +0,0 @@
1
- module Larynx
2
- class NoPromptDefined < StandardError; end
3
-
4
- module Fields
5
-
6
- def self.included(base)
7
- base.extend ClassMethods
8
- base.class_eval do
9
- include InstanceMethods
10
- class_inheritable_accessor :field_definitions
11
- self.field_definitions = []
12
- attr_accessor :fields
13
- end
14
- end
15
-
16
- module ClassMethods
17
-
18
- def field(name, options={}, &block)
19
- self.field_definitions << {:name => name, :options => options, :block => block}
20
- attr_accessor name
21
- end
22
-
23
- end
24
-
25
- module InstanceMethods
26
-
27
- def initialize(*args, &block)
28
- @fields = self.class.field_definitions.map {|field| Field.new(field[:name], field[:options], &field[:block]) }
29
- @field_index = -1
30
- super
31
- end
32
-
33
- def next_field(field_name=nil)
34
- if field_name
35
- @field_index = field_index(field_name)
36
- else
37
- @field_index += 1
38
- end
39
- if field = current_field
40
- field.run(self)
41
- field
42
- end
43
- end
44
-
45
- def current_field
46
- @fields[@field_index]
47
- end
48
-
49
- def field_index(name)
50
- field = @fields.find {|f| f.name == name }
51
- @fields.index(field)
52
- end
53
-
54
- def attempt
55
- current_field.attempt
56
- end
57
-
58
- def last_attempt?
59
- current_field.last_attempt?
60
- end
61
-
62
- end
63
-
64
- class Field
65
- include CallbacksWithAsync
66
-
67
- VALID_PROMPT_OPTIONS = [:play, :speak, :phrase, :bargein, :repeats, :interdigit_timeout, :timeout]
68
-
69
- attr_reader :name, :app, :attempt
70
- define_callback :setup, :validate, :invalid, :success, :failure, :scope => :app
71
-
72
- def initialize(name, options, &block)
73
- @name = name
74
- @options = options.reverse_merge(:attempts => 3)
75
- @prompt_queue = []
76
-
77
- instance_eval(&block)
78
- raise(Larynx::NoPromptDefined, 'A field requires a prompt to be defined') if @prompt_queue.empty?
79
- end
80
-
81
- def prompt(options)
82
- add_prompt(options)
83
- end
84
-
85
- def reprompt(options)
86
- raise 'A reprompt can only be used after a prompt' if @prompt_queue.empty?
87
- add_prompt(options)
88
- end
89
-
90
- def add_prompt(options)
91
- options.assert_valid_keys(*VALID_PROMPT_OPTIONS)
92
- repeats = options.delete(:repeats) || 1
93
- options.merge!(@options.slice(:length, :min_length, :max_length, :interdigit_timeout, :timeout))
94
- @prompt_queue += ([options] * repeats)
95
- end
96
-
97
- def current_prompt
98
- options = (@prompt_queue[@attempt-1] || @prompt_queue.last).dup
99
- method = command_from_options(options)
100
- message = options[method].is_a?(Symbol) ? @app.send(options[method]) : options[method]
101
- options[method] = message
102
-
103
- Prompt.new(call, options) {|input, result|
104
- set_instance_variables(input, result)
105
- evaluate_input
106
- }
107
- end
108
-
109
- def execute_prompt
110
- call.execute current_prompt.command
111
- send_next_command
112
- end
113
-
114
- def increment_attempts
115
- @attempt += 1
116
- end
117
-
118
- # hook called when callback is complete
119
- def callback_complete(callback, result=true)
120
- case callback
121
- when :validate
122
- evaluate_validity(result)
123
- when :invalid
124
- invalid_input
125
- when :success, :failure
126
- finalize
127
- end
128
- end
129
-
130
- def evaluate_input
131
- @valid_length ? fire_callback(:validate) : fire_callback(:invalid)
132
- end
133
-
134
- def evaluate_validity(result)
135
- result ? fire_callback(:success) : fire_callback(:invalid)
136
- end
137
-
138
- def invalid_input
139
- if last_attempt?
140
- fire_callback(:failure)
141
- else
142
- increment_attempts
143
- execute_prompt
144
- end
145
- end
146
-
147
- def send_next_command
148
- call.send_next_command if call.state == :ready
149
- end
150
-
151
- def set_instance_variables(input, result)
152
- @value, @valid_length = input, result
153
- @app.send("#{@name}=", input)
154
- end
155
-
156
- def command_from_options(options)
157
- (Prompt::COMMAND_OPTIONS & options.keys).first
158
- end
159
-
160
- def run(app)
161
- @app = app
162
- @attempt = 1
163
- call.add_observer self
164
- fire_callback(:setup)
165
- execute_prompt
166
- end
167
-
168
- def call
169
- @app.call
170
- end
171
-
172
- def finalize
173
- call.remove_observer self
174
- send_next_command
175
- end
176
-
177
- def last_attempt?
178
- @attempt == @options[:attempts]
179
- end
180
-
181
- end
182
-
183
- end
184
- end
@@ -1,271 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- class TestApp < Larynx::Application; end
4
-
5
- describe Larynx::Fields do
6
- attr_reader :call, :app
7
-
8
- before do
9
- @call = TestCallHandler.new(1)
10
- @app = define_app.new(@call)
11
- end
12
-
13
- context 'module' do
14
- before do
15
- @app_class = define_app
16
- end
17
-
18
- it 'should add field class method' do
19
- @app_class.should respond_to(:field)
20
- end
21
-
22
- it 'should add instance accessor for field name' do
23
- @app_class.field(:guess) { prompt :speak => 'hello' }
24
- @app_class.methods.include?(:guess)
25
- end
26
- end
27
-
28
- context '#next_field' do
29
- before do
30
- @app = define_app do
31
- field(:field1) { prompt :speak => 'hello' }
32
- field(:field2) { prompt :speak => 'hello' }
33
- field(:field3) { prompt :speak => 'hello' }
34
- end.new(call)
35
- end
36
-
37
- it 'should iterate over defined fields' do
38
- app.next_field.name.should == :field1
39
- app.next_field.name.should == :field2
40
- app.next_field.name.should == :field3
41
- end
42
-
43
- it 'should jump to field name if supplied' do
44
- app.next_field(:field2).name.should == :field2
45
- end
46
- end
47
-
48
- context "#current_field" do
49
- it 'should return field of current position' do
50
- @app = define_app do
51
- field(:field1) { prompt :speak => 'hello' }
52
- field(:field2) { prompt :speak => 'hello' }
53
- end.new(call)
54
- app.run
55
-
56
- app.next_field
57
- app.current_field.should == app.fields[0]
58
- app.next_field
59
- app.current_field.should == app.fields[1]
60
- end
61
- end
62
-
63
- context 'field object' do
64
- it 'should raise exception if field has no prompt' do
65
- lambda { field(:guess) {} }.should raise_exception(Larynx::NoPromptDefined)
66
- end
67
-
68
- it 'should run setup callback once' do
69
- call_me = should_be_called
70
- fld = field(:guess) do
71
- prompt :speak => 'first'
72
- setup &call_me
73
- end
74
- fld.run app
75
- end
76
-
77
- it 'should pass timeout and length options to the prompt object' do
78
- fld = field(:guess, :length => 1, :min_length => 1, :max_length => 2, :interdigit_timeout => 1, :timeout => 2) do
79
- prompt :speak => 'first'
80
- end
81
- fld.run(app)
82
- prompt = fld.current_prompt
83
- prompt.interdigit_timeout.should == 1
84
- prompt.timeout.should == 2
85
- prompt.minimum_length.should == 1
86
- prompt.maximum_length.should == 2
87
- end
88
-
89
- it 'should return same prompt all attempts if single prompt' do
90
- fld = field(:guess) do
91
- prompt :speak => 'first'
92
- end
93
- fld.run(app)
94
- fld.current_prompt.message.should == 'first'
95
- fld.increment_attempts
96
- fld.current_prompt.message.should == 'first'
97
- end
98
-
99
- it 'should return reprompt for subsequent prompts' do
100
- fld = field(:guess) do
101
- prompt :speak => 'first'
102
- reprompt :speak => 'second'
103
- end
104
- fld.run(app)
105
- fld.current_prompt.message.should == 'first'
106
- fld.increment_attempts
107
- fld.current_prompt.message.should == 'second'
108
- end
109
-
110
- it 'should return prompt for given number of repeats before subsequent prompts' do
111
- fld = field(:guess) do
112
- prompt :speak => 'first', :repeats => 2
113
- reprompt :speak => 'second'
114
- end
115
- fld.run(app)
116
- fld.current_prompt.message.should == 'first'
117
- fld.increment_attempts
118
- fld.current_prompt.message.should == 'first'
119
- fld.increment_attempts
120
- fld.current_prompt.message.should == 'second'
121
- end
122
-
123
- context "#last_attempt?" do
124
- it 'should return false when current attempt not equal to max attempts' do
125
- fld = field(:guess, :attempts => 2) do
126
- prompt :speak => 'first'
127
- end
128
- fld.run(app)
129
- fld.attempt.should == 1
130
- fld.last_attempt?.should be_false
131
- end
132
-
133
- it 'should return true when current attempt equals max attempts' do
134
- fld = field(:guess, :attempts => 2) do
135
- prompt :speak => 'first'
136
- end
137
- fld.run(app)
138
- fld.increment_attempts
139
- fld.attempt.should == 2
140
- fld.last_attempt?.should be_true
141
- end
142
- end
143
-
144
- context 'input evaluation' do
145
- it 'should run validate callback if input minimum length' do
146
- call_me = should_be_called
147
- fld = field(:guess, :min_length => 1) do
148
- prompt :speak => 'first'
149
- validate &call_me
150
- end
151
- fld.run app
152
- call.input << '1'
153
- fld.current_prompt.finalise
154
- end
155
-
156
- it 'should run invalid callback if length not valid' do
157
- call_me = should_be_called
158
- fld = field(:guess) do
159
- prompt :speak => 'first'
160
- invalid &call_me
161
- end
162
- fld.run app
163
- fld.current_prompt.finalise
164
- end
165
-
166
- it 'should run invalid callback if validate callback returns false' do
167
- call_me = should_be_called
168
- fld = field(:guess, :min_length => 1) do
169
- prompt :speak => 'first'
170
- validate { false }
171
- invalid &call_me
172
- end
173
- fld.run app
174
- call.input << '1'
175
- fld.current_prompt.finalise
176
- end
177
-
178
- it 'should run invalid callback if validate callback returns nil' do
179
- call_me = should_be_called
180
- fld = field(:guess, :min_length => 1) do
181
- prompt :speak => 'first'
182
- validate { nil }
183
- invalid &call_me
184
- end
185
- fld.run app
186
- call.input << '1'
187
- fld.current_prompt.finalise
188
- end
189
-
190
- it 'should run success callback if length valid and no validate callback' do
191
- call_me = should_be_called
192
- fld = field(:guess, :min_length => 1) do
193
- prompt :speak => 'first'
194
- success &call_me
195
- end
196
- fld.run app
197
- call.input << '1'
198
- fld.current_prompt.finalise
199
- end
200
-
201
- it 'should run success callback if validate callback returns true' do
202
- call_me = should_be_called
203
- fld = field(:guess, :min_length => 1) do
204
- prompt :speak => 'first'
205
- validate { true }
206
- success &call_me
207
- end
208
- fld.run app
209
- call.input << '1'
210
- fld.current_prompt.finalise
211
- end
212
-
213
- it 'should run failure callback if not valid and last attempt' do
214
- call_me = should_be_called
215
- fld = field(:guess, :min_length => 1, :attempts => 1) do
216
- prompt :speak => 'first'
217
- failure &call_me
218
- end
219
- fld.run app
220
- fld.current_prompt.finalise
221
- end
222
-
223
- it 'should increment attempts if not valid' do
224
- fld = field(:guess) do
225
- prompt :speak => 'first'
226
- reprompt :speak => 'second'
227
- end
228
- fld.run app
229
- fld.current_prompt.finalise
230
- fld.current_prompt.message.should == 'second'
231
- end
232
-
233
- it 'should execute next prompt if not valid' do
234
- fld = field(:guess) do
235
- prompt :speak => 'first'
236
- reprompt :speak => 'second'
237
- end
238
- fld.run app
239
- fld.should_receive(:execute_prompt)
240
- fld.current_prompt.finalise
241
- end
242
- end
243
-
244
- context "async callbacks" do
245
- # it "should be run in thread" do
246
- # em do
247
- # fld = field(:guess) do
248
- # prompt :speak => 'first'
249
- # validate(:async) { sleep(0.25) }
250
- # success { done }
251
- # end.run(app)
252
- # call.input << '1'
253
- # end
254
- # @callback.should be_nil
255
- # end
256
- end
257
-
258
- end
259
-
260
- def field(name, options={}, &block)
261
- @app.class.class_eval { attr_accessor name }
262
- Larynx::Fields::Field.new(name, options, &block)
263
- end
264
-
265
- def define_app(&block)
266
- reset_class(TestApp) do
267
- include Larynx::Fields
268
- instance_eval &block if block_given?
269
- end
270
- end
271
- end