rene-adhearsion 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/CHANGELOG +73 -0
  2. data/EVENTS +11 -0
  3. data/LICENSE +456 -0
  4. data/Rakefile +130 -0
  5. data/adhearsion.gemspec +173 -0
  6. data/app_generators/ahn/USAGE +5 -0
  7. data/app_generators/ahn/ahn_generator.rb +96 -0
  8. data/app_generators/ahn/templates/.ahnrc +34 -0
  9. data/app_generators/ahn/templates/README +8 -0
  10. data/app_generators/ahn/templates/Rakefile +25 -0
  11. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  12. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  13. data/app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown +11 -0
  14. data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +48 -0
  15. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +91 -0
  16. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml +34 -0
  17. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +263 -0
  18. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +104 -0
  19. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml +2 -0
  20. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  21. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  22. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.yml +12 -0
  23. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/README.markdown +3 -0
  24. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +11 -0
  25. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
  26. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  27. data/app_generators/ahn/templates/config/startup.rb +83 -0
  28. data/app_generators/ahn/templates/dialplan.rb +3 -0
  29. data/app_generators/ahn/templates/events.rb +32 -0
  30. data/bin/ahn +28 -0
  31. data/bin/ahnctl +68 -0
  32. data/bin/jahn +42 -0
  33. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  34. data/lib/adhearsion.rb +45 -0
  35. data/lib/adhearsion/cli.rb +228 -0
  36. data/lib/adhearsion/component_manager.rb +272 -0
  37. data/lib/adhearsion/component_manager/component_tester.rb +55 -0
  38. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  39. data/lib/adhearsion/events_support.rb +84 -0
  40. data/lib/adhearsion/foundation/all.rb +15 -0
  41. data/lib/adhearsion/foundation/blank_slate.rb +3 -0
  42. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  43. data/lib/adhearsion/foundation/event_socket.rb +204 -0
  44. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  45. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  46. data/lib/adhearsion/foundation/numeric.rb +13 -0
  47. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  48. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  49. data/lib/adhearsion/foundation/string.rb +26 -0
  50. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  51. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  52. data/lib/adhearsion/host_definitions.rb +67 -0
  53. data/lib/adhearsion/initializer.rb +395 -0
  54. data/lib/adhearsion/initializer/asterisk.rb +87 -0
  55. data/lib/adhearsion/initializer/configuration.rb +321 -0
  56. data/lib/adhearsion/initializer/database.rb +60 -0
  57. data/lib/adhearsion/initializer/drb.rb +31 -0
  58. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  59. data/lib/adhearsion/initializer/ldap.rb +57 -0
  60. data/lib/adhearsion/initializer/rails.rb +41 -0
  61. data/lib/adhearsion/initializer/xmpp.rb +42 -0
  62. data/lib/adhearsion/logging.rb +92 -0
  63. data/lib/adhearsion/tasks.rb +16 -0
  64. data/lib/adhearsion/tasks/database.rb +5 -0
  65. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  66. data/lib/adhearsion/tasks/generating.rb +20 -0
  67. data/lib/adhearsion/tasks/lint.rb +4 -0
  68. data/lib/adhearsion/tasks/testing.rb +37 -0
  69. data/lib/adhearsion/version.rb +33 -0
  70. data/lib/adhearsion/voip/asterisk.rb +4 -0
  71. data/lib/adhearsion/voip/asterisk/agi_server.rb +115 -0
  72. data/lib/adhearsion/voip/asterisk/commands.rb +1510 -0
  73. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  74. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  75. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  76. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  77. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  78. data/lib/adhearsion/voip/asterisk/manager_interface.rb +705 -0
  79. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1680 -0
  80. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +340 -0
  81. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  82. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  83. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  84. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  85. data/lib/adhearsion/voip/call.rb +497 -0
  86. data/lib/adhearsion/voip/call_routing.rb +64 -0
  87. data/lib/adhearsion/voip/commands.rb +9 -0
  88. data/lib/adhearsion/voip/constants.rb +39 -0
  89. data/lib/adhearsion/voip/conveniences.rb +18 -0
  90. data/lib/adhearsion/voip/dial_plan.rb +246 -0
  91. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  92. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  93. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  94. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  95. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +69 -0
  96. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  97. data/lib/adhearsion/voip/dsl/numerical_string.rb +115 -0
  98. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  99. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  100. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  101. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  102. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  103. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  104. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  105. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  106. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  107. data/lib/adhearsion/xmpp/connection.rb +61 -0
  108. data/lib/theatre.rb +151 -0
  109. data/lib/theatre/README.markdown +64 -0
  110. data/lib/theatre/callback_definition_loader.rb +84 -0
  111. data/lib/theatre/guid.rb +23 -0
  112. data/lib/theatre/invocation.rb +121 -0
  113. data/lib/theatre/namespace_manager.rb +153 -0
  114. data/lib/theatre/version.rb +2 -0
  115. metadata +241 -0
@@ -0,0 +1,80 @@
1
+ module Adhearsion
2
+ class DialPlan
3
+ class ConfirmationManager
4
+
5
+ class << self
6
+
7
+ def encode_hash_for_dial_macro_argument(options)
8
+ options = options.clone
9
+ macro_name = options.delete :macro
10
+ options[:play] &&= options[:play].kind_of?(Array) ? options[:play].join('++') : options[:play]
11
+ encoded_options = URI.escape options.map { |key,value| "#{key}:#{value}" }.join('!')
12
+ "M(#{macro_name}^#{encoded_options})".tap do |str|
13
+ if str.rindex('^') != str.index('^')
14
+ raise ArgumentError, "You seem to have supplied a :confirm option with a caret (^) in it!" +
15
+ " Please remove it. This will blow Asterisk up."
16
+ end
17
+ end
18
+ end
19
+
20
+ def handle(call)
21
+ new(call).handle
22
+ end
23
+
24
+ def confirmation_call?(call)
25
+ call.variables.has_key?(:network_script) && call.variables[:network_script].starts_with?('confirm!')
26
+ end
27
+
28
+ def decode_hash(encoded_hash)
29
+ encoded_hash = encoded_hash =~ /^M\((.+)\)$/ ? $1 : encoded_hash
30
+ encoded_hash = encoded_hash =~ /^([^:]+\^)?(.+)$/ ? $2 : encoded_hash # Remove the macro name if it's there
31
+ unencoded = URI.unescape(encoded_hash).split('!')
32
+ unencoded.shift unless unencoded.first.include?(':')
33
+ unencoded = unencoded.map { |pair| key, value = pair.split(':'); [key.to_sym ,value] }.flatten
34
+ Hash[*unencoded].tap do |hash|
35
+ hash[:timeout] &&= hash[:timeout].to_i
36
+ hash[:play] &&= hash[:play].split('++')
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ attr_reader :call
43
+ def initialize(call)
44
+ @call = call
45
+ extend Adhearsion::VoIP::Commands.for(call.originating_voip_platform)
46
+ end
47
+
48
+ def handle
49
+ variables = self.class.decode_hash call.variables[:network_script]
50
+
51
+ answer
52
+ loop do
53
+ response = interruptible_play(*variables[:play])
54
+ if response && response.to_s == variables[:key].to_s
55
+ # Don't set a variable to pass through to dial()
56
+ break
57
+ elsif response && response.to_s != variables[:key].to_s
58
+ next
59
+ else
60
+ response = wait_for_digit variables[:timeout]
61
+ if response
62
+ if response.to_s == variables[:key].to_s
63
+ # Don't set a variable to pass through to dial()
64
+ break
65
+ else
66
+ next
67
+ end
68
+ else
69
+ # By setting MACRO_RESULT to CONTINUE, we cancel the dial.
70
+ variable 'MACRO_RESULT' => "CONTINUE"
71
+ break
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ module Asterisk
4
+ module Manager
5
+
6
+ ##
7
+ # Higher level abstraction of the Asterisk Manager Interface.
8
+ #
9
+ class SuperManager
10
+
11
+ def initialize
12
+ raise NotImplementedError
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,497 @@
1
+ require 'uri'
2
+ require 'thread'
3
+ #TODO Some of this is asterisk-specific
4
+ module Adhearsion
5
+ class << self
6
+ def active_calls
7
+ @calls ||= Calls.new
8
+ end
9
+
10
+ def receive_call_from(io, &block)
11
+ active_calls << (call = Call.receive_from(io, &block))
12
+ call
13
+ end
14
+
15
+ def remove_inactive_call(call)
16
+ active_calls.remove_inactive_call(call)
17
+ end
18
+ end
19
+
20
+ class Hangup < StandardError
21
+ # At the moment, we'll just use this to end a call-handling Thread.
22
+ end
23
+
24
+ ##
25
+ # This manages the list of calls the Adhearsion service receives
26
+ class Calls
27
+ def initialize
28
+ @semaphore = Monitor.new
29
+ @calls = {}
30
+ end
31
+
32
+ def <<(call)
33
+ atomically do
34
+ calls[call.unique_identifier] = call
35
+ end
36
+ end
37
+
38
+ def any?
39
+ atomically do
40
+ !calls.empty?
41
+ end
42
+ end
43
+
44
+ def size
45
+ atomically do
46
+ calls.size
47
+ end
48
+ end
49
+
50
+ def remove_inactive_call(call)
51
+ atomically do
52
+ calls.delete call.unique_identifier
53
+ end
54
+ end
55
+
56
+ # Searches all active calls by their unique_identifier. See Call#unique_identifier.
57
+ # Is this actually by channel?
58
+ def find(id)
59
+ atomically do
60
+ return calls[id]
61
+ end
62
+ end
63
+
64
+ def clear!
65
+ atomically do
66
+ calls.clear
67
+ end
68
+ end
69
+
70
+ def with_tag(tag)
71
+ atomically do
72
+ calls.inject(Array.new) do |calls_with_tag,(key,call)|
73
+ call.tagged_with?(tag) ? calls_with_tag << call : calls_with_tag
74
+ end
75
+ end
76
+ end
77
+
78
+ def to_a
79
+ calls.values
80
+ end
81
+
82
+ private
83
+ attr_reader :semaphore, :calls
84
+
85
+ def atomically(&block)
86
+ semaphore.synchronize(&block)
87
+ end
88
+
89
+ end
90
+
91
+ class UselessCallException < StandardError; end
92
+
93
+ class MetaAgiCallException < StandardError
94
+ attr_reader :call
95
+ def initialize(call)
96
+ super()
97
+ @call = call
98
+ end
99
+ end
100
+
101
+ class FailedExtensionCallException < MetaAgiCallException; end
102
+
103
+ class HungupExtensionCallException < MetaAgiCallException; end
104
+
105
+ ##
106
+ # Encapsulates call-related data and behavior.
107
+ # For example, variables passed in on call initiation are
108
+ # accessible here as attributes
109
+ class Call
110
+
111
+ ##
112
+ # Wraps the Queue object (subclasses it) so we can handle runaway threads,
113
+ # namely those originating from using with_next_message in commands.rb - this
114
+ # overrides << to check for the :cancel symbol and trigger the automessaging_tainted
115
+ # instance variable.
116
+ class CallMessageQueue < Queue
117
+
118
+ class InboxClosedException < StandardError
119
+ # this gets raised when the :cancel message is delivered to the queue and with_next_message (or similar auto-message-iteration)
120
+ # features are called.
121
+ end
122
+
123
+ attr_reader :open
124
+
125
+ def initialize
126
+ @open = true
127
+ super
128
+ end
129
+
130
+ def <<(queue_obj)
131
+ @open = false if queue_obj == :cancel
132
+ super(queue_obj)
133
+ end
134
+
135
+ def pop
136
+ raise Adhearsion::Call::CallMessageQueue::InboxClosedException, "The message queue for this call has aleady been disabled." if !@open
137
+ super
138
+ end
139
+
140
+
141
+ end
142
+
143
+
144
+ # This is basically a translation of ast_channel_reason2str() from main/channel.c and
145
+ # ast_control_frame_type in include/asterisk/frame.h in the Asterisk source code. When
146
+ # Asterisk jumps to the 'failed' extension, it sets a REASON channel variable to a number.
147
+ # The indexes of these symbols represent the possible numbers REASON could be.
148
+ ASTERISK_FRAME_STATES = [
149
+ :failure, # "Call Failure (not BUSY, and not NO_ANSWER, maybe Circuit busy or down?)"
150
+ :hangup, # Other end has hungup
151
+ :ring, # Local ring
152
+ :ringing, # Remote end is ringing
153
+ :answer, # Remote end has answered
154
+ :busy, # Remote end is busy
155
+ :takeoffhook, # Make it go off hook
156
+ :offhook, # Line is off hook
157
+ :congestion, # Congestion (circuits busy)
158
+ :flash, # Flash hook
159
+ :wink, # Wink
160
+ :option, # Set a low-level option
161
+ :radio_key, # Key Radio
162
+ :radio_unkey, # Un-Key Radio
163
+ :progress, # Indicate PROGRESS
164
+ :proceeding, # Indicate CALL PROCEEDING
165
+ :hold, # Indicate call is placed on hold
166
+ :unhold, # Indicate call is left from hold
167
+ :vidupdate # Indicate video frame update
168
+ ]
169
+
170
+
171
+ class << self
172
+ ##
173
+ # The primary public interface for creating a Call instance.
174
+ # Given an IO (probably a socket accepted from an Asterisk service),
175
+ # creates a Call instance which encapsulates everything we know about that call.
176
+ def receive_from(io, &block)
177
+ new(io, variable_parser_for(io).variables).tap do |call|
178
+ block.call(call) if block
179
+ end
180
+ end
181
+
182
+ private
183
+ def variable_parser_for(io)
184
+ Variables::Parser.parse(io)
185
+ end
186
+
187
+ end
188
+
189
+ attr_accessor :io, :type, :variables, :originating_voip_platform, :inbox
190
+ def initialize(io, variables)
191
+ @io, @variables = io, variables.symbolize_keys
192
+ check_if_valid_call
193
+ define_variable_accessors
194
+ set_originating_voip_platform!
195
+ @tag_mutex = Mutex.new
196
+ @tags = []
197
+ end
198
+
199
+ def tags
200
+ @tag_mutex.synchronize do
201
+ return @tags.clone
202
+ end
203
+ end
204
+
205
+ # This may still be a symbol, but no longer requires the tag to be a symbol although beware
206
+ # that using a symbol would create a memory leak if used improperly
207
+ # @param [String, Symbol] label String or Symbol with which to tag this call
208
+ def tag(label)
209
+ if ![String, Symbol].include?(label.class)
210
+ raise ArgumentError, "Tag must be a String or Symbol"
211
+ end
212
+ @tag_mutex.synchronize do
213
+ @tags << label
214
+ end
215
+ end
216
+
217
+ def remove_tag(symbol)
218
+ @tag_mutex.synchronize do
219
+ @tags.reject! { |tag| tag == symbol }
220
+ end
221
+ end
222
+
223
+ def tagged_with?(symbol)
224
+ @tag_mutex.synchronize do
225
+ @tags.include? symbol
226
+ end
227
+ end
228
+
229
+ def deliver_message(message)
230
+ inbox << message
231
+ end
232
+ alias << deliver_message
233
+
234
+ def can_use_messaging?
235
+ return inbox.open == true
236
+ end
237
+
238
+ def inbox
239
+ @inbox ||= CallMessageQueue.new
240
+ end
241
+
242
+ def hangup!
243
+ io.close
244
+ Adhearsion.remove_inactive_call self
245
+ end
246
+
247
+ def closed?
248
+ io.closed?
249
+ end
250
+
251
+ # Asterisk sometimes uses the "failed" extension to indicate a failed dial attempt.
252
+ # Since it may be important to handle these, this flag helps the dialplan Manager
253
+ # figure that out.
254
+ def failed_call?
255
+ @failed_call
256
+ end
257
+
258
+ def hungup_call?
259
+ @hungup_call
260
+ end
261
+
262
+ # Adhearsion indexes calls by this identifier so they may later be found and manipulated. For calls from Asterisk, this
263
+ # method uses the following properties for uniqueness, falling back to the next if one is for some reason unavailable:
264
+ #
265
+ # Asterisk channel ID -> unique ID -> Call#object_id
266
+ # (e.g. SIP/mytrunk-jb12c88a) -> (e.g. 1215039989.47033) -> (e.g. 2792080)
267
+ #
268
+ # Note: channel is used over unique ID because channel may be used to bridge two channels together.
269
+ def unique_identifier
270
+ case originating_voip_platform
271
+ when :asterisk
272
+ variables[:channel] || variables[:uniqueid] || object_id
273
+ else
274
+ raise NotImplementedError
275
+ end
276
+ end
277
+
278
+ def define_variable_accessors(recipient=self)
279
+ variables.each do |key, value|
280
+ define_singleton_accessor_with_pair(key, value, recipient)
281
+ end
282
+ end
283
+
284
+ def extract_failed_reason_from(environment)
285
+ if originating_voip_platform == :asterisk
286
+ failed_reason = environment.variable 'REASON'
287
+ failed_reason &&= ASTERISK_FRAME_STATES[failed_reason.to_i]
288
+ define_singleton_accessor_with_pair(:failed_reason, failed_reason, environment)
289
+ end
290
+ end
291
+
292
+ private
293
+
294
+ def define_singleton_accessor_with_pair(key, value, recipient=self)
295
+ recipient.metaclass.send :attr_accessor, key unless recipient.class.respond_to?("#{key}=")
296
+ recipient.send "#{key}=", value
297
+ end
298
+
299
+ def check_if_valid_call
300
+ extension = variables[:extension]
301
+ @failed_call = true if extension == 'failed'
302
+ @hungup_call = true if extension == 'h'
303
+ raise UselessCallException if extension == 't' # TODO: Move this whole method to Manager
304
+ end
305
+
306
+ def set_originating_voip_platform!
307
+ # TODO: we can make this determination programatically at some point,
308
+ # but it will probably involve a bit more engineering than just a case statement (like
309
+ # subclasses of Call for the various platforms), so we'll be totally cheap for now.
310
+ self.originating_voip_platform = :asterisk
311
+ end
312
+
313
+ module Variables
314
+
315
+ module Coercions
316
+
317
+ COERCION_ORDER = %w{
318
+ remove_agi_prefixes_from_keys_and_strip_whitespace
319
+ coerce_keys_into_symbols
320
+ coerce_extension_into_phone_number_object
321
+ coerce_numerical_values_to_numerics
322
+ replace_unknown_values_with_nil
323
+ replace_yes_no_answers_with_booleans
324
+ coerce_request_into_uri_object
325
+ decompose_uri_query_into_hash
326
+ override_variables_with_query_params
327
+ remove_dashes_from_context_name
328
+ coerce_type_of_number_into_symbol
329
+ }
330
+
331
+ class << self
332
+
333
+ def remove_agi_prefixes_from_keys_and_strip_whitespace(variables)
334
+ variables.inject({}) do |new_variables,(key,value)|
335
+ new_variables.tap do
336
+ stripped_name = key.kind_of?(String) ? key[/^(agi_)?(.+)$/,2] : key
337
+ new_variables[stripped_name] = value.kind_of?(String) ? value.strip : value
338
+ end
339
+ end
340
+ end
341
+
342
+ def coerce_keys_into_symbols(variables)
343
+ variables.inject({}) do |new_variables,(key,value)|
344
+ new_variables.tap do
345
+ new_variables[key.to_sym] = value
346
+ end
347
+ end
348
+ end
349
+
350
+ def coerce_extension_into_phone_number_object(variables)
351
+ variables.tap do
352
+ variables[:extension] = Adhearsion::VoIP::DSL::PhoneNumber.new(variables[:extension])
353
+ end
354
+ end
355
+
356
+ def coerce_numerical_values_to_numerics(variables)
357
+ variables.inject({}) do |vars,(key,value)|
358
+ vars.tap do
359
+ is_numeric = value =~ /^-?\d+(?:(\.)\d+)?$/
360
+ is_float = $1
361
+ vars[key] = if is_numeric
362
+ if Adhearsion::VoIP::DSL::NumericalString.starts_with_leading_zero?(value)
363
+ Adhearsion::VoIP::DSL::NumericalString.new(value)
364
+ else
365
+ if is_float
366
+ if key == :uniqueid
367
+ value
368
+ else
369
+ value.to_f
370
+ end
371
+ else
372
+ value.to_i
373
+ end
374
+ end
375
+ else
376
+ value
377
+ end
378
+ end
379
+ end
380
+ end
381
+
382
+ def replace_unknown_values_with_nil(variables)
383
+ variables.each do |key,value|
384
+ variables[key] = nil if value == 'unknown'
385
+ end
386
+ end
387
+
388
+ def replace_yes_no_answers_with_booleans(variables)
389
+ variables.each do |key,value|
390
+ case value
391
+ when 'yes' then variables[key] = true
392
+ when 'no' then variables[key] = false
393
+ end
394
+ end
395
+ end
396
+
397
+ def coerce_request_into_uri_object(variables)
398
+ if variables[:request]
399
+ variables[:request] = URI.parse(variables[:request]) unless variables[:request].kind_of? URI
400
+ end
401
+ variables
402
+ end
403
+
404
+ def coerce_type_of_number_into_symbol(variables)
405
+ variables.tap do
406
+ variables[:type_of_calling_number] = Adhearsion::VoIP::Constants::Q931_TYPE_OF_NUMBER[variables.delete(:callington).to_i]
407
+ end
408
+ end
409
+
410
+ def decompose_uri_query_into_hash(variables)
411
+ variables.tap do
412
+ if variables[:request] && variables[:request].query
413
+ variables[:query] = variables[:request].query.split('&').inject({}) do |query_string_parameters, key_value_pair|
414
+ parameter_name, parameter_value = *key_value_pair.match(/(.+)=(.*)/).captures
415
+ query_string_parameters[parameter_name] = parameter_value
416
+ query_string_parameters
417
+ end
418
+ else
419
+ variables[:query] = {}
420
+ end
421
+ end
422
+ end
423
+
424
+ def override_variables_with_query_params(variables)
425
+ variables.tap do
426
+ if variables[:query]
427
+ variables[:query].each do |key, value|
428
+ variables[key.to_sym] = value
429
+ end
430
+ end
431
+ end
432
+ end
433
+
434
+ def remove_dashes_from_context_name(variables)
435
+ variables.tap do
436
+ variables[:context].gsub!('-', '_')
437
+ end
438
+ end
439
+
440
+ end
441
+ end
442
+
443
+ class Parser
444
+
445
+ class << self
446
+ def parse(*args, &block)
447
+ new(*args, &block).tap do |parser|
448
+ parser.parse
449
+ end
450
+ end
451
+
452
+ def coerce_variables(variables)
453
+ Coercions::COERCION_ORDER.inject(variables) do |tmp_variables, coercing_method_name|
454
+ Coercions.send(coercing_method_name, tmp_variables)
455
+ end
456
+ end
457
+
458
+ def separate_line_into_key_value_pair(line)
459
+ line.match(/^([^:]+):\s?(.+)/).captures
460
+ end
461
+ end
462
+
463
+ attr_reader :io, :variables, :lines
464
+ def initialize(io)
465
+ @io = io
466
+ @lines = []
467
+ end
468
+
469
+ def parse
470
+ extract_variable_lines_from_io
471
+ initialize_variables_as_hash_from_lines
472
+ @variables = self.class.coerce_variables(variables)
473
+ end
474
+
475
+ private
476
+
477
+ def initialize_variables_as_hash_from_lines
478
+ @variables = lines.inject({}) do |new_variables,line|
479
+ new_variables.tap do
480
+ key, value = self.class.separate_line_into_key_value_pair line
481
+ new_variables[key] = value
482
+ end
483
+ end
484
+ end
485
+
486
+ def extract_variable_lines_from_io
487
+ while line = io.readline.chomp
488
+ break if line.empty?
489
+ @lines << line
490
+ end
491
+ end
492
+
493
+ end
494
+
495
+ end
496
+ end
497
+ end