larynx 0.1.5 → 0.1.6

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