larynx 0.1.4 → 0.1.5

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/README.rdoc CHANGED
@@ -212,6 +212,10 @@ together and define another form for unrelated fields.
212
212
  Lets look at a simple Form class with empty callbacks in place.
213
213
 
214
214
  class MyApp < Larynx::Form
215
+ setup do
216
+ # Run when the form is first run or restarted.
217
+ end
218
+
215
219
  field(:my_field, :attempts => 3, :length => 1) do
216
220
  prompt :speak => 'Please enter a value.'
217
221
 
data/Rakefile CHANGED
@@ -23,7 +23,6 @@ spec = Gem::Specification.new do |s|
23
23
  s.homepage = "http://github.com/adzap/larynx"
24
24
 
25
25
  s.require_path = 'lib'
26
- s.autorequire = GEM_NAME
27
26
  s.files = %w(MIT-LICENSE README.rdoc Rakefile) + Dir.glob("{lib,spec,examples}/**/*")
28
27
  s.add_dependency "eventmachine", "~> 0.12.10"
29
28
  s.add_dependency "activesupport", "~> 2.3.5"
@@ -1,7 +1,9 @@
1
1
  module Larynx
2
2
  class Application
3
3
  attr_reader :call
4
+
4
5
  delegate *Commands.instance_methods << {:to => :call}
6
+ delegate :session, :to => :call
5
7
 
6
8
  def self.run(call)
7
9
  app = self.new(call)
@@ -107,7 +107,7 @@ module Larynx
107
107
  finalize_command
108
108
  @state = :ready
109
109
  Larynx.fire_callback(:answer, self)
110
- send_next_command
110
+ send_next_command if @state == :ready
111
111
  when @response.executing?
112
112
  log "Executing: #{current_command.name}"
113
113
  current_command.setup
@@ -168,6 +168,7 @@ module Larynx
168
168
  break! if @state == :executing
169
169
  cancel_all_timers
170
170
  clear_observers!
171
+ @session = nil
171
172
  end
172
173
 
173
174
  def log(msg)
@@ -41,7 +41,7 @@ module Larynx
41
41
  callback_complete(callback, scope_callback(block, scope).call)
42
42
  end
43
43
  else
44
- callback_complete(callback, nil)
44
+ callback_complete(callback)
45
45
  end
46
46
  end
47
47
 
data/lib/larynx/fields.rb CHANGED
@@ -26,30 +26,47 @@ module Larynx
26
26
 
27
27
  def initialize(*args, &block)
28
28
  @fields = self.class.field_definitions.map {|field| Field.new(field[:name], field[:options], &field[:block]) }
29
- @current_field = 0
29
+ @field_index = -1
30
30
  super
31
31
  end
32
32
 
33
33
  def next_field(field_name=nil)
34
- @current_field = field_index(field_name) if field_name
35
- if field = @fields[@current_field]
34
+ if field_name
35
+ @field_index = field_index(field_name)
36
+ else
37
+ @field_index += 1
38
+ end
39
+ if field = current_field
36
40
  field.run(self)
37
- @current_field += 1
38
41
  field
39
42
  end
40
43
  end
41
44
 
45
+ def current_field
46
+ @fields[@field_index]
47
+ end
48
+
42
49
  def field_index(name)
43
50
  field = @fields.find {|f| f.name == name }
44
51
  @fields.index(field)
45
52
  end
46
53
 
54
+ def attempt
55
+ current_field.attempt
56
+ end
57
+
58
+ def last_attempt?
59
+ current_field.last_attempt?
60
+ end
61
+
47
62
  end
48
63
 
49
64
  class Field
50
65
  include CallbacksWithAsync
51
66
 
52
- attr_reader :name, :app
67
+ VALID_PROMPT_OPTIONS = [:play, :speak, :phrase, :bargein, :repeats, :interdigit_timeout, :timeout]
68
+
69
+ attr_reader :name, :app, :attempt
53
70
  define_callback :setup, :validate, :invalid, :success, :failure, :scope => :app
54
71
 
55
72
  def initialize(name, options, &block)
@@ -71,7 +88,7 @@ module Larynx
71
88
  end
72
89
 
73
90
  def add_prompt(options)
74
- options.assert_valid_keys(:play, :speak, :phrase, :bargein, :repeats, :interdigit_timeout, :timeout)
91
+ options.assert_valid_keys(*VALID_PROMPT_OPTIONS)
75
92
  repeats = options.delete(:repeats) || 1
76
93
  options.merge!(@options.slice(:length, :min_length, :max_length, :interdigit_timeout, :timeout))
77
94
  @prompt_queue += ([options] * repeats)
@@ -99,10 +116,9 @@ module Larynx
99
116
  end
100
117
 
101
118
  # hook called when callback is complete
102
- def callback_complete(callback, result)
119
+ def callback_complete(callback, result=true)
103
120
  case callback
104
121
  when :validate
105
- result = result.nil? ? true : result
106
122
  evaluate_validity(result)
107
123
  when :invalid
108
124
  invalid_input
@@ -120,11 +136,11 @@ module Larynx
120
136
  end
121
137
 
122
138
  def invalid_input
123
- if @attempt < @options[:attempts]
139
+ if last_attempt?
140
+ fire_callback(:failure)
141
+ else
124
142
  increment_attempts
125
143
  execute_prompt
126
- else
127
- fire_callback(:failure)
128
144
  end
129
145
  end
130
146
 
@@ -138,7 +154,7 @@ module Larynx
138
154
  end
139
155
 
140
156
  def command_from_options(options)
141
- ([:play, :speak, :phrase] & options.keys).first
157
+ (Prompt::COMMAND_OPTIONS & options.keys).first
142
158
  end
143
159
 
144
160
  def run(app)
@@ -158,6 +174,10 @@ module Larynx
158
174
  send_next_command
159
175
  end
160
176
 
177
+ def last_attempt?
178
+ @attempt == @options[:attempts]
179
+ end
180
+
161
181
  end
162
182
 
163
183
  end
data/lib/larynx/form.rb CHANGED
@@ -1,19 +1,19 @@
1
1
  module Larynx
2
2
  class Form < Application
3
3
  include Fields
4
- @@setup = nil
4
+ class_inheritable_accessor :setup_block
5
5
 
6
6
  def self.setup(&block)
7
- @@setup = block
7
+ self.setup_block = block
8
8
  end
9
9
 
10
10
  def run
11
- instance_eval &@@setup if @@setup
11
+ instance_eval &self.class.setup_block if self.class.setup_block
12
12
  next_field
13
13
  end
14
14
 
15
15
  def restart_form
16
- @current_field = 0
16
+ @field_index = -1
17
17
  run
18
18
  end
19
19
  end
data/lib/larynx/prompt.rb CHANGED
@@ -13,6 +13,8 @@ module Larynx
13
13
  class Prompt
14
14
  attr_reader :call
15
15
 
16
+ COMMAND_OPTIONS = [:play, :speak, :phrase]
17
+
16
18
  def initialize(call, options, &block)
17
19
  @call, @options, @block = call, options, block
18
20
  @options.reverse_merge!(:bargein => true, :timeout => 10, :interdigit_timeout => 3, :termchar => '#')
@@ -21,8 +23,9 @@ module Larynx
21
23
 
22
24
  def command
23
25
  @command ||= AppCommand.new(command_name, message, :bargein => @options[:bargein]).
24
- before { call.clear_input }.
26
+ before { call.clear_input unless @options[:bargein] }.
25
27
  after {
28
+ call.clear_input unless @options[:bargein]
26
29
  if prompt_finished?
27
30
  finalise
28
31
  else
@@ -62,7 +65,7 @@ module Larynx
62
65
  end
63
66
 
64
67
  def command_name
65
- ([:play, :speak, :phrase] & @options.keys).first.to_s
68
+ (COMMAND_OPTIONS & @options.keys).first.to_s
66
69
  end
67
70
 
68
71
  def message
@@ -0,0 +1,95 @@
1
+ require 'logger'
2
+ require 'daemons/daemonize'
3
+
4
+ module Larynx
5
+ module Server
6
+ class << self
7
+
8
+ def boot
9
+ parse_options(ARGV)
10
+ setup_app
11
+ daemonize if @options[:daemonize]
12
+ setup_logger
13
+ trap_signals
14
+ start_server
15
+ end
16
+
17
+ def parse_options(args=ARGV)
18
+ @options = {
19
+ :ip => "0.0.0.0",
20
+ :port => 8084,
21
+ :pid_file => "./larynx.pid",
22
+ :log_file => "./larynx.log"
23
+ }
24
+ opts = OptionParser.new
25
+ opts.banner = "Usage: larynx [options] app_file"
26
+ opts.separator ''
27
+ opts.separator "Larynx is a framework to develop FreeSWITCH IVR applications in Ruby."
28
+ opts.on('-i', '--ip IP', 'Listen for connections on this IP') {|ip| @options[:ip] = ip }
29
+ opts.on('-p', '--port PORT', 'Listen on this port', Integer) {|port| @options[:port] = port }
30
+ opts.on('-d', '--daemonize', 'Run as daemon') { @options[:daemonize] = true }
31
+ opts.on('-l', '--log-file FILE', 'Defaults to /app/root/larynx.log') {|log| @options[:log_file] = log }
32
+ opts.on( '--pid-file FILE', 'Defaults to /app/root/larynx.pid') {|pid| @options[:pid_file] = pid }
33
+ opts.on('-h', '--help', 'This is it') { $stderr.puts opts; exit 0 }
34
+ opts.on('-v', '--version') { $stderr.puts "Larynx version #{Larynx::VERSION}"; exit 0 }
35
+ opts.parse!(args)
36
+ end
37
+
38
+ def setup_logger
39
+ logger = Larynx::Logger.new(@options[:log_file])
40
+ logger.level = Logger::INFO
41
+ Object.const_set "LARYNX_LOGGER", logger
42
+ end
43
+
44
+ def graceful_exit
45
+ msg = "Shutting down Larynx"
46
+ $stderr.puts msg unless @options[:daemon]
47
+ LARYNX_LOGGER.info msg
48
+
49
+ EM.stop_server @em_signature
50
+ @em_signature = nil
51
+ remove_pid_file if @options[:daemonize]
52
+ exit 130
53
+ end
54
+
55
+ def daemonize
56
+ Daemonize.daemonize
57
+ Dir.chdir LARYNX_ROOT
58
+ File.open(@options[:pid_file], 'w+') {|f| f.write("#{Process.pid}\n") }
59
+ end
60
+
61
+ def remove_pid_file
62
+ File.delete @options[:pid_file]
63
+ end
64
+
65
+ def trap_signals
66
+ trap('TERM') { graceful_exit }
67
+ trap('INT') { graceful_exit }
68
+ end
69
+
70
+ def setup_app
71
+ if ARGV[0].nil?
72
+ $stderr.puts "You must specify an application file"
73
+ exit -1
74
+ end
75
+ Object.const_set "LARYNX_ROOT", File.expand_path(File.dirname(ARGV[0]))
76
+ require File.expand_path(ARGV[0])
77
+ end
78
+
79
+ def start_server
80
+ msg = "Larynx starting up on #{@options[:ip]}:#{@options[:port]}"
81
+ $stderr.puts msg unless @options[:daemon]
82
+ LARYNX_LOGGER.info msg
83
+
84
+ EM::run {
85
+ @em_signature = EM::start_server @options[:ip], @options[:port], Larynx::CallHandler
86
+ }
87
+ end
88
+
89
+ def running?
90
+ !@em_signature.nil?
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -12,6 +12,10 @@ module Larynx
12
12
  end
13
13
  end
14
14
 
15
+ def []=(key, value)
16
+ @variables[key] = value
17
+ end
18
+
15
19
  def [](key)
16
20
  @variables[key]
17
21
  end
@@ -1,3 +1,3 @@
1
1
  module Larynx
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.5'
3
3
  end
@@ -198,6 +198,18 @@ describe Larynx::CallHandler do
198
198
  end
199
199
  end
200
200
 
201
+ it "should send next command if state is ready" do
202
+ call.should_receive(:send_next_command)
203
+ answer_call
204
+ end
205
+
206
+ it "should not send next command if answer callback changed state" do
207
+ call.should_not_receive(:send_next_command)
208
+ with_global_callback(:answer, lambda { call.state = :sending }) do
209
+ answer_call
210
+ end
211
+ end
212
+
201
213
  def answer_call
202
214
  call.send_response :answered
203
215
  end
@@ -25,7 +25,7 @@ describe Larynx::Fields do
25
25
  end
26
26
  end
27
27
 
28
- context 'next_field' do
28
+ context '#next_field' do
29
29
  before do
30
30
  @app = define_app do
31
31
  field(:field1) { prompt :speak => 'hello' }
@@ -45,6 +45,21 @@ describe Larynx::Fields do
45
45
  end
46
46
  end
47
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
+
48
63
  context 'field object' do
49
64
  it 'should raise exception if field has no prompt' do
50
65
  lambda { field(:guess) {} }.should raise_exception(Larynx::NoPromptDefined)
@@ -105,6 +120,27 @@ describe Larynx::Fields do
105
120
  fld.current_prompt.message.should == 'second'
106
121
  end
107
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
+
108
144
  context 'input evaluation' do
109
145
  it 'should run validate callback if input minimum length' do
110
146
  call_me = should_be_called
@@ -139,6 +175,18 @@ describe Larynx::Fields do
139
175
  fld.current_prompt.finalise
140
176
  end
141
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
+
142
190
  it 'should run success callback if length valid and no validate callback' do
143
191
  call_me = should_be_called
144
192
  fld = field(:guess, :min_length => 1) do
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ class TestForm < Larynx::Form; end
4
+
5
+ describe Larynx::Form do
6
+ attr_reader :call
7
+
8
+ before do
9
+ @call = TestCallHandler.new(1)
10
+ end
11
+
12
+ context "#run" do
13
+ it 'should call setup block' do
14
+ this_should_be_called = should_be_called
15
+ define_form do
16
+ setup &this_should_be_called
17
+ end.run(call)
18
+ end
19
+ end
20
+
21
+ context "#restart_form" do
22
+ it 'should call form setup block' do
23
+ this_should_be_called = should_be_called
24
+ form = define_form do
25
+ setup &this_should_be_called
26
+ end.new(call)
27
+ form.restart_form
28
+ end
29
+
30
+ it 'should run the first field again' do
31
+ form = define_form do
32
+ field(:test1) { prompt :speak => '' }
33
+ field(:test2) { prompt :speak => '' }
34
+ end.new(call)
35
+
36
+ form.fields[0].should_receive(:run).twice
37
+ form.run
38
+ form.next_field
39
+ form.restart_form
40
+ end
41
+ end
42
+
43
+ def define_form(&block)
44
+ reset_class(TestForm) do
45
+ instance_eval &block if block_given?
46
+ end
47
+ end
48
+
49
+ end
@@ -39,6 +39,27 @@ describe Larynx::Prompt do
39
39
  @prompt.input.should == '1'
40
40
  end
41
41
  end
42
+ context "command" do
43
+ it "should return command object for command name" do
44
+ cmd = new_prompt.command
45
+ cmd.should be_kind_of(Larynx::AppCommand)
46
+ cmd.command.should == 'speak'
47
+ end
48
+ end
49
+
50
+ context "before callback" do
51
+ it "should clear input on execution if no bargein allowed" do
52
+ call.input << '1'
53
+ before_callback new_prompt(:speak => 'hello', :bargein => false)
54
+ call.input.should be_empty
55
+ end
56
+
57
+ it "should clear input on execution if bargein allowed" do
58
+ call.input << '1'
59
+ before_callback new_prompt(:speak => 'hello', :bargein => true)
60
+ call.input.should_not be_empty
61
+ end
62
+ end
42
63
 
43
64
  context "prompt_finished?" do
44
65
  it "should return true if input length reached" do
@@ -66,23 +87,28 @@ describe Larynx::Prompt do
66
87
  end
67
88
  end
68
89
 
69
- context "command" do
70
- it "should return command object for command name" do
71
- cmd = new_prompt.command
72
- cmd.should be_kind_of(Larynx::AppCommand)
73
- cmd.command.should == 'speak'
74
- end
75
- end
76
90
 
77
- context "before callback" do
78
- it "should clear input on execution" do
79
- call.input << '1'
80
- before_callback new_prompt
81
- call.input.should be_empty
91
+ context "after callback" do
92
+ context "bargein" do
93
+ it 'should clear input before prompt status evaluated if false' do
94
+ prompt = new_prompt(:speak => 'hello', :length => 1, :bargein => false)
95
+ call.input << '1'
96
+ em do
97
+ after_callback prompt
98
+ prompt.prompt_finished?.should be_false
99
+ done
100
+ end
101
+ end
102
+
103
+ it 'should not clear input before prompt status evaluated if true' do
104
+ prompt = new_prompt(:speak => 'hello', :length => 1, :bargein => true) { done }
105
+ call.input << '1'
106
+ em do
107
+ after_callback prompt
108
+ end
109
+ end
82
110
  end
83
- end
84
111
 
85
- context "after callback" do
86
112
  context "input completed" do
87
113
  it "should not add timers if reached length" do
88
114
  prompt = new_prompt
@@ -146,9 +172,10 @@ describe Larynx::Prompt do
146
172
  end
147
173
 
148
174
  it "should clear input" do
175
+ call.input << '1'
149
176
  prompt = new_prompt
150
- call.should_receive(:clear_input)
151
177
  prompt.finalise
178
+ call.input.should be_empty
152
179
  end
153
180
  end
154
181
 
metadata CHANGED
@@ -1,21 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: larynx
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 4
10
- version: 0.1.4
9
+ - 5
10
+ version: 0.1.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam Meehan
14
- autorequire: larynx
14
+ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-26 00:00:00 +10:00
18
+ date: 2010-09-03 00:00:00 +10:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -107,6 +107,7 @@ files:
107
107
  - lib/larynx/prompt.rb
108
108
  - lib/larynx/response.rb
109
109
  - lib/larynx/restartable_timer.rb
110
+ - lib/larynx/server.rb
110
111
  - lib/larynx/session.rb
111
112
  - lib/larynx/version.rb
112
113
  - lib/larynx.rb
@@ -122,6 +123,7 @@ files:
122
123
  - spec/larynx/command_spec.rb
123
124
  - spec/larynx/eventmachince_spec.rb
124
125
  - spec/larynx/fields_spec.rb
126
+ - spec/larynx/form_spec.rb
125
127
  - spec/larynx/prompt_spec.rb
126
128
  - spec/larynx_spec.rb
127
129
  - spec/spec_helper.rb