adhearsion 0.7.0

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.
Files changed (48) hide show
  1. data/LICENSE +339 -0
  2. data/Rakefile +108 -0
  3. data/ahn +195 -0
  4. data/lib/adhearsion.rb +402 -0
  5. data/lib/constants.rb +20 -0
  6. data/lib/core_extensions.rb +157 -0
  7. data/lib/database_functions.rb +76 -0
  8. data/lib/rami.rb +822 -0
  9. data/lib/servlet_container.rb +146 -0
  10. data/new_projects/Rakefile +100 -0
  11. data/new_projects/config/adhearsion.sqlite3 +0 -0
  12. data/new_projects/config/adhearsion.yml +11 -0
  13. data/new_projects/config/database.rb +50 -0
  14. data/new_projects/config/database.yml +10 -0
  15. data/new_projects/config/helpers/drb_server.yml +43 -0
  16. data/new_projects/config/helpers/factorial.alien.c.yml +1 -0
  17. data/new_projects/config/helpers/manager_proxy.yml +7 -0
  18. data/new_projects/config/helpers/micromenus.yml +1 -0
  19. data/new_projects/config/helpers/micromenus/collab.rb +55 -0
  20. data/new_projects/config/helpers/micromenus/images/tux.bmp +0 -0
  21. data/new_projects/config/helpers/micromenus/javascripts/builder.js +131 -0
  22. data/new_projects/config/helpers/micromenus/javascripts/controls.js +834 -0
  23. data/new_projects/config/helpers/micromenus/javascripts/dragdrop.js +944 -0
  24. data/new_projects/config/helpers/micromenus/javascripts/effects.js +956 -0
  25. data/new_projects/config/helpers/micromenus/javascripts/prototype.js +2319 -0
  26. data/new_projects/config/helpers/micromenus/javascripts/scriptaculous.js +51 -0
  27. data/new_projects/config/helpers/micromenus/javascripts/slider.js +278 -0
  28. data/new_projects/config/helpers/micromenus/javascripts/unittest.js +557 -0
  29. data/new_projects/config/helpers/micromenus/stylesheets/firefox.css +10 -0
  30. data/new_projects/config/helpers/micromenus/stylesheets/firefox.xul.css +44 -0
  31. data/new_projects/config/helpers/weather.yml +1 -0
  32. data/new_projects/config/helpers/xbmc.yml +1 -0
  33. data/new_projects/config/migration.rb +53 -0
  34. data/new_projects/extensions.rb +56 -0
  35. data/new_projects/helpers/drb_server.rb +32 -0
  36. data/new_projects/helpers/factorial.alien.c +32 -0
  37. data/new_projects/helpers/manager_proxy.rb +43 -0
  38. data/new_projects/helpers/micromenus.rb +374 -0
  39. data/new_projects/helpers/oscar_wilde_quotes.rb +197 -0
  40. data/new_projects/helpers/weather.rb +85 -0
  41. data/new_projects/helpers/xbmc.rb +12 -0
  42. data/new_projects/logs/database.log +0 -0
  43. data/test/core_extensions_test.rb +26 -0
  44. data/test/dial_test.rb +43 -0
  45. data/test/stress_tests/test.rb +13 -0
  46. data/test/stress_tests/test.yml +13 -0
  47. data/test/test_micromenus.rb +0 -0
  48. metadata +131 -0
@@ -0,0 +1,402 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'core_extensions'
19
+
20
+ module Asterisk
21
+
22
+ # The exec method allows any traditional Asterisk applications to be called
23
+ # within Adhearsion. The first argument should be the case-insensitive name
24
+ # of the application and any arguments needed by the application can simply
25
+ # by trailed onto the end of the method. For a full list of Asterisk's
26
+ # applications, see "this":http://www.voip-info.org/wiki/index.php?page=Asterisk+-+documentation+of+application+commands
27
+ def exec(app, *options)
28
+ result = rawr "EXEC #{app} " + (options * '|')
29
+ result = result[/-?\d+$/]
30
+ result == "-2" ? false : result
31
+ end
32
+
33
+ # This method of receiving user input uses the fantastic Asterisk
34
+ # Read() application, but its results are pretty inconsitent over
35
+ # AGI. This code has been kept here in case these bugs are fixed.
36
+ def old_input digits=nil, user_options=Hash.new('')
37
+ used_options = {:variable => String.random, :digits => digits}
38
+ used_options.default = ''
39
+ used_options.merge! user_options
40
+ args = []
41
+ %w(variable soundfile digits option attempts timeout).each do |x|
42
+ args << used_options[x.to_sym]
43
+ end
44
+ log "THESE ARE THE INPUT ARGS: " + args.inspect
45
+ exec :read, args
46
+ $OSCAR[:VARS] << args.first
47
+ x = get_variable(args.first).gsub(/[\(\)]/, "")
48
+ puts "RETURN: #{x.inspect}"
49
+ x.simplify
50
+ end
51
+
52
+ # Input is used to receive keypad input from the user, pausing until they
53
+ # have entered the desired number of digits (specified with the first
54
+ # parameter) or the timeout has been reached (specified as a hash argument
55
+ # with the key :timeout). By default, there is no timeout, waiting infinitely.
56
+ #
57
+ # If you desire a sound to be played other than a simple beep to instruct
58
+ # the callee to input data, pass the filename as an hash argument with either
59
+ # the :play or :file key.
60
+ #
61
+ # When called without any arguments (or a first argument of -1), the user is
62
+ # able to enter digits ad infinitum until they press the pound (#) key.
63
+ def input digits=nil, hash={}
64
+ timeout, file = (hash[:timeout] || -1), (hash[:play] || hash[:file] || 'beep')
65
+ result = rawr "GET DATA #{file} #{timeout} #{digits}"
66
+ result = result[/\=-?\d+/]
67
+ result ? result[1..-1] : false
68
+ end
69
+
70
+ # Does as you'd imagine: returns the amount of time the given block takes to
71
+ # execute. This is particularly useful in VoIP since billing and such often
72
+ # requires time keeping beyond the Call Detail Records. This method is also
73
+ # aliased to bill() as well.
74
+ def time
75
+ return 0 unless block_given?
76
+ start = Time.now
77
+ yield start
78
+ Time.now - start
79
+ end
80
+ alias bill time
81
+
82
+ # Requires an AMI connection! Given a block, everything contained
83
+ # within it will be recorded. If no arguments are given, a String
84
+ # for the filename will be generated from the current time and a
85
+ # sequence of random alphanumeric characters.
86
+ def record hash, &block
87
+ defaults = {:file => "#{String.random(5)}", :folder => nil,
88
+ :channel => Thread.current[:VARS]['channel'], :format => 'wav', :mix => '1'}
89
+ defaults = defaults.merge hash
90
+ #PBX.record
91
+ end
92
+
93
+ # Waits for single digit numpad key response from the user. If no timeout argument
94
+ # is given, the system will wait indefinitely. The argument is the desired time
95
+ # to wait fo0r the response in seconds. Feel free to use the ActiveSupport extensions
96
+ # for using this method like wait_for_digit(2.minutes) or something similar. When the
97
+ # timeout is encountered (or an error occurs receiving the input), the method
98
+ # returns nil. If the asterisk or pound key was pressed, a String is returned of that
99
+ # response. In all other cases, the Fixnum of the number pressed is returned.
100
+ def wait_for_digit timeout=-1
101
+ digit = rawr("WAIT FOR DIGIT #{timeout < 0 ? -1 : timeout / 1000.0}").match(/=(.*)$/)[1].to_i
102
+ return nil if digit <= 0 # If there was an error or timeout
103
+ return digit.chr if [32,45].include? digit
104
+ digit - ?0
105
+ end
106
+
107
+ # An abstracted remote mutator for setting Asterisk variables.
108
+ def set_variable(key,value) rawr "SET VARIABLE #{key} #{value}" end
109
+
110
+ # An abstracted remote accessor for retrieving Asterisk variables.
111
+ def get_variable(key, default=nil)
112
+ result = rawr "GET VARIABLE #{key}"
113
+ result[0..12] == "200 result=0" ? default : result[13..-1]
114
+ end
115
+
116
+ def dial who, *options
117
+ #group = who.group if who.respond_to :group
118
+ exec :dial, properize(who)
119
+ end
120
+
121
+ # The stream_file() method comes right over from the AGI protocol.
122
+ # Its use is very similar to the Background() application from
123
+ # Asterisk's extensions.conf language. A file is played in the
124
+ # background while optionally waiting for input. In the event
125
+ # the second argument is given, Asterisk will listen for that
126
+ # sequence of digits and return control to Adhearsion, informing
127
+ # us of the digits the user pressed. These digits are returned
128
+ # as a String. If you would also like to know the ending position
129
+ # at which the streaming stopped, see stream_file_with_offset().
130
+ def stream_file file, digits='', offset=0
131
+ stream_file_with_offset(file, digits, offset).first
132
+ end
133
+
134
+ # Similar to stream_file(), but returning an array of length 2
135
+ # instead. If supplied a first argument, the return value will
136
+ # be digits pressed by the user to end
137
+ def stream_file_with_offset file, digits='', offset=0
138
+ response = rawr "STREAM FILE #{file} #{digits} #{offset}"
139
+ return nil unless response.starts_with? '200'
140
+
141
+ result_index = response.index(?=) + 1
142
+ spacer_index = response.rindex(' ')
143
+ endpos_index = spacer_index + 8 # " endpos=".length == 8
144
+
145
+ result = response[result_index...spacer_index].to_i
146
+ endpos = response[endpos_index..-1].to_i
147
+
148
+ return [nil,nil] if (result.zero? && endpos.zero?) || result == -1
149
+
150
+ [result, endpos]
151
+ end
152
+
153
+ # Festival is pretty buggy (and pretty inefficient). In theory,
154
+ # this should speak out any text supplied to it.
155
+ def speak text
156
+ text.gsub!('\n').gsub!('\r').strip!
157
+ exec :festival, "'#{text}'"
158
+ end
159
+
160
+ # Since voicemail is used so frequently, extra effort has been made to
161
+ # make it syntactically sweet. voicemail() takes the mailbox number of
162
+ # a user as its first or second argument and the mailbox type as its
163
+ # first or second argument (the order doesn't matter). If a specific
164
+ # voicemail context is necessary, trail it to the end of the method
165
+ # with the ":at => 'contextname'" syntax. The mailbox type can be
166
+ # :busy, :unavailable, or :normal, though if no type is given, the
167
+ # type :normal is assumed.
168
+ #
169
+ # Usage: voicemail :busy, 4544
170
+ # or: voicemail 4544
171
+ # or: voicemail 4544, :unavailable, :at => 'codemecca'
172
+ def voicemail msg_or_who=nil, who_or_msg=nil, hash={}
173
+ opts = [msg_or_who, who_or_msg]
174
+ type = opts & [:normal, :busy, :unavailable]
175
+ box = opts - type
176
+ box.last <<= "@#{hash[:at]}}" if hash[:at]
177
+ exec :Voicemail, *(type + box)
178
+ end
179
+
180
+ # Execute VoiceMailMain behind the scenes to check a specified
181
+ # user's voicemail inbox or, if no arguments are passed, prompt
182
+ # the user for their mailbox number.
183
+ #
184
+ # Usage: check_voicemail 'jay', :at => 'default', :args => 's'
185
+ # or : check_voicemail
186
+ # or : check_voicemail 'hubbard'
187
+ def check_voicemail name=nil, hash={}
188
+ args = [ [name, hash[:at]].compact * '@', hash[:args]]
189
+ exec :VoiceMailMain, args.compact
190
+ end
191
+
192
+ # Play is a long-overdue easy way to play audio files in a dialplan with Asterisk. It can take a
193
+ # single String of the filename (defaults to /var/lib/asterisk/sounds) just as you
194
+ # would give it to Playback(), or it can take any number of trailed on Strings.
195
+ # If you pass it an Array, it will traverse the array, playing each of the items
196
+ # in order. If you're doing this, make your life *incredibly* easier by using the
197
+ # fantastic Ruby Array literal for Arrays of Strings: %w(). Within this, one
198
+ # specifies separate, simple Strings by placing whitespace bewtween them. Since
199
+ # no Asterisk files contain whitespace, this works _very_ well! Example:
200
+ #
201
+ # play %w(a-connect-charge-of 22 cents-per-minute will-apply)
202
+ #
203
+ # Note here that numbers can be play()ed as well. By convention, play() will call
204
+ # SayNumber() on these indices instead of Playback().
205
+ def play *files
206
+ files.flatten!
207
+ files.each do |f|
208
+ if f.simplify.is_a? Fixnum then exec :saynumber, f
209
+ else exec :playback, f
210
+ end
211
+ end
212
+ end
213
+
214
+ # The rawr() method is the main way of receiving a raw response from the Asterisk
215
+ # server. When no argument is given, it will immediately ask for a response,
216
+ # returning that String. When an argument +is+ given, it will first send that
217
+ # command and then return the response that command generated. Everything is
218
+ # chomp()ed before returned. Pun here totally intended.
219
+ def rawr(what=nil)
220
+ begin
221
+ putc what if what
222
+ PBX.io.gets.chomp!
223
+ rescue => e
224
+ #log "Socket no longer available for communication. Exceptions will likely occur."
225
+ end
226
+ end
227
+
228
+ # Simply print()s the command over the AGI IO socket.
229
+ def putc(what) PBX.io.print what end
230
+
231
+ # The magical method that handles how objects passed to dial() are converted to their
232
+ # corresponding Asterisk-recognizable technology/extension identifier. Likely wouldn't
233
+ # be used much outside of dial().
234
+ def properize who
235
+ possible_methods = [:users, :members, :user, :member].select { |pm| who.respond_to? pm } # These are the convention methods for Group-like objects
236
+ who = who.send possible_methods.first if possible_methods.any? # If 'who' has any of the possible_methods, replace 'who' with the return value of that method
237
+ return unless who
238
+ who = [who] unless who.kind_of?(Enumerable) && !who.kind_of?(String) # In case we can't perform collection algorithms on 'who', let's encapsulate it in an Array
239
+ # If the first thing in the Enumerable responds to a User-like convention, then set 'who' equal to the extension accessor of those objects
240
+ who.map! { |p| p.send possible_methods.first }.compact! if (possible_methods = [:extension, :extensions].select { |pm| who.first.respond_to? pm }).any?
241
+ # Now replace each item in the Enumerable with its form converted to extension (assuming it's not already in that format)
242
+ who.map! do |ext|
243
+ if ext.kind_of?(String) && ext.index(?/) then ext
244
+ else
245
+ ext = "1#{ext}" if ext.is_national_number? && ext.to_s[0] != ?1
246
+ "SIP/#{ext}"
247
+ end
248
+ end
249
+ who *= '&' # Finally, join() anything left in the Array with an '&'
250
+ end
251
+
252
+ # Returns the status of the last dial(). Possible dial
253
+ # statuses include :answer, :busy, :noanswer, :cancel,
254
+ # :congestion, and :chanunavail. If :cancel is
255
+ # returned, the caller hung up before the callee picked
256
+ # up. If :congestion is returned, the dialed extension
257
+ # probably doesn't exist. If :chanunavail, the callee
258
+ # phone may not be registered.
259
+ def last_dial_status
260
+ get_variable(:DIALSTATUS).downcase.to_sym
261
+ end
262
+
263
+ # Answer the channel. Adhearsion is configured by default to automatically do this
264
+ # when a call comes in.
265
+ def answer() rawr 'ANSWER' end
266
+ # Hangs up the channel. Adhearsion is configured by default to matically do this
267
+ # when a context completes execution
268
+ def hangup() rawr 'HANGUP' end
269
+ # Direct translation of the Asterisk NoOp() application. Used primarily for
270
+ # viewing debug information in the Asterisk CLI.
271
+ def noop(*options) rawr "NOOP #{options}" end
272
+
273
+ # The SIP/ZAP/IAX/IAX2/Zap methods allow for cleaner addressing of particular
274
+ # extensions, abstracting Asterisk's representation. If not given any
275
+ # argument, this methods simply return a symbol identifying their
276
+ # appropriate protocol.
277
+
278
+ # See the SIP class
279
+ def SIP(ext=nil) ext ? "SIP/#{ext}" : :SIP end
280
+ # See the ZAP class
281
+ def ZAP(ext=nil) ext ? "Zap/#{ext}" : :ZAP end
282
+ # See the IAX class
283
+ def IAX(ext=nil) ext ? "IAX2/#{ext}" : :IAX end
284
+ alias IAX2 IAX
285
+ alias Zap ZAP
286
+
287
+ # For the completely selfish purpose of sugaring the syntax, two tiny holder classes
288
+ # are created to allow the synonym of SIP(1404) to be SIP[1404]. Both functions,
289
+ # when given an argument, return a String that Asterisk understands representing that
290
+ # particular channel.
291
+
292
+ # Syntax sugar for declaring SIP devices. Use: SIP/123 or SIP[123]
293
+ class SIP
294
+ def self.[](arg=nil) SIP(arg) end
295
+ def self./(arg=nil) SIP(arg) end
296
+ end
297
+ # Syntax sugar for declaring IAX devices. Use: IAX/123 or IAX[123]
298
+ class IAX
299
+ def self.[](arg=nil) IAX(arg) end
300
+ def self./(arg=nil) IAX(arg) end
301
+ end
302
+
303
+ # Syntax sugar for declaring ZAP devices. Use: ZAP/123 or ZAP[123]
304
+ class ZAP
305
+ def self.[](arg=nil) ZAP(arg) end
306
+ def self./(arg=nil) ZAP(arg) end
307
+ end
308
+
309
+ # For users who may try to use the (proper) IAX2 form. See IAX for more info.
310
+ class IAX2 < IAX; end
311
+
312
+ # For users who may try to use the (common) "Zap" form. See ZAP for more info.
313
+ class Zap < ZAP; end
314
+ end
315
+
316
+ # The PBX object is an object manifestation of the PBX with which Adhearsion will be
317
+ # associated. Helpers often open up this class and add methods to it. Note: when doing
318
+ # this in your own helpers, all methods will need to be class (a.k.a. static) methods
319
+ # since no actual instance of this object is passed around.
320
+ class PBX
321
+ def PBX.io() Thread.current[:io] end
322
+ end
323
+
324
+ # The Contexts class is a blank slate in which the extensions.rb file is evaluated.
325
+ # Its method_missing() simply takes a block and, given the name of attempted method,
326
+ # meta_def()s a new accessor method in Contexts with this name that returns the block
327
+ # given. This is how contexts can be included anywhere in extensions.rb using the
328
+ # easy "+context_name" syntax.
329
+ class Contexts
330
+
331
+ (instance_methods - %w(__send__ __id__ define_method instance_eval)).each do |m|
332
+ undef_method m
333
+ end
334
+
335
+ def method_missing name, *args, &block
336
+ super(name, *args, &block) unless block
337
+ Thread.current[:container].run_inside do
338
+ meta_def name do
339
+ block
340
+ end
341
+ end
342
+ end
343
+
344
+ # This Container object is a container in which each context is executed (not instantiated).
345
+ # It extends the Asterisk module from adhearsion.rb and thus inherits all of the functionality
346
+ # declared there. This class can be monkey patched if necessary.
347
+ class Container
348
+ include Asterisk
349
+ def initialize
350
+ class << self
351
+ def metaclass; class << self; self; end; end
352
+ def meta_eval &blk; metaclass.instance_eval(&blk); end
353
+ def meta_def name, &blk
354
+ meta_eval { define_method name, &blk }
355
+ end
356
+ end
357
+ end
358
+
359
+ def method_missing name, *args, &block
360
+ Kernel.method_missing name, *args, &block
361
+ end
362
+
363
+ def run_inside &code
364
+ instance_eval(&code)
365
+ end
366
+
367
+ def eval_inside code
368
+ run_inside do
369
+ eval code
370
+ end
371
+ end
372
+ end
373
+ end
374
+
375
+ # An Exception thrown when the directory supplied in the constructor of a
376
+ # new RailsApp object is invalid.
377
+ class InvalidRailsDirectory < Exception;end
378
+
379
+ # When instantiated with an absolute location to a Rails app, the new RailsApp will perform
380
+ # a number of useful observations about the files and directories available, such as loading
381
+ # the database configuration and listing all of the models. All observations are made accessible
382
+ # through attribute accessors.
383
+ class RailsApp
384
+ def initialize path=Dir.pwd
385
+ update! path
386
+ end
387
+
388
+ # Performs the observations on the Rails app once more.
389
+ def update! path
390
+ @path = path
391
+ @database_config_file = File.join @path, 'config', 'database.yml'
392
+ if File.readable? @database_config_file
393
+ @database_config = YAML.load_file @database_config_file
394
+ else raise InvalidRailsDirectory.new("Database config file #{@database_config_file} not found!")
395
+ end
396
+ @models_folder = File.join path, 'app', 'models'
397
+ @models_files = Dir[ File.join(@models_folder, '*.rb') ]
398
+ @time_updated = Time.now
399
+ end
400
+ attr_reader :database_config_file, :database_config, :path, :models_folder, :models_files,
401
+ :models_names, :time_updated
402
+ end
@@ -0,0 +1,20 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ # Please help adjust these if they may be inaccurate!
19
+ LOCAL_NUMBER = /^[1-9]\d{6}$/
20
+ US_NUMBER = /^1?[1-9]\d{2})?[1-9]\d{6}$/
@@ -0,0 +1,157 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'active_support'
19
+
20
+ class Object
21
+
22
+ # For dealing with data that may be more difficult to manage than it should be.
23
+ # Strings with simplify() called on them return integers if they appear to be
24
+ # integers. Arrays with only one item in it return ary.first.simplify.
25
+ def simplify
26
+ if is_a? String
27
+ return Integer(self) if self =~ /^\d+$/
28
+ elsif is_a? Array
29
+ return first.simplify if size == 1
30
+ end
31
+ self
32
+ end
33
+ def is_a_group?
34
+ # TODO: check if it responds to the conventions.
35
+ end
36
+ def is_a_user?
37
+ # TODO: check if it responds to the conventions.
38
+ end
39
+
40
+ def is_local_number?
41
+ to_s =~ LOCAL_NUMBER
42
+ end
43
+
44
+ def is_national_number?
45
+ to_s =~ national_number
46
+ end
47
+
48
+ def mutex() @mutex ||= Mutex.new end
49
+ def synchronize() mutex.synchronize { yield self } end
50
+
51
+ private
52
+
53
+ $BEFORE_CALL, $BEFORE_CALL_HIGH, $BEFORE_CALL_LOW = [],[],[]
54
+ def before_call priority=:normal, &block
55
+ name = priority == :normal ? "BEFORE_CALL" : "BEFORE_CALL_#{priority.to_s.upcase}"
56
+ eval("$#{name}") << block
57
+ end
58
+
59
+ $AFTER_CALL, $AFTER_CALL_HIGH, $AFTER_CALL_LOW = [],[],[]
60
+ def after_call priority=:normal, &block
61
+ name = priority == :normal ? "AFTER_CALL" : "AFTER_CALL_#{priority.to_s.upcase}"
62
+ eval("$#{name}") << block
63
+ end
64
+ end
65
+
66
+ class String
67
+
68
+ def String.random_char
69
+ case r = rand(62)
70
+ when 0...10 then r.to_s
71
+ when 10...36 then (r+55).chr
72
+ when 36...62 then (r+61).chr
73
+ end
74
+ end
75
+
76
+ # Handy way to generate random strings of an arbitrary lengths.
77
+ def String.random len=8
78
+ str = ''
79
+ len.times do str << String.random_char end
80
+ str
81
+ end
82
+ def nameify() downcase.gsub(/[^\w]/, '') end
83
+ def nameify!() replace nameify end
84
+ end
85
+
86
+ class Proc
87
+ def +@
88
+ Thread.current[:container].run_inside(&self)
89
+ true # Allows "and" operator
90
+ end
91
+
92
+ def ~@
93
+ raise ControlPassingException.new(self)
94
+ end
95
+ end
96
+
97
+
98
+ # A ControlPassingException is used internally to stop execution of one
99
+ # dialplan context and begin execution of another proc instead. It is
100
+ # most notably used by the ~@ unary operator that can be called on a
101
+ # context name within a dialplan to transfer control entirely to that
102
+ # particular context. The serve() method in the servlet_container actually
103
+ # rescues these exceptions specifically and then does +e.target to execute
104
+ # that code.
105
+ class ControlPassingException < Exception
106
+ def initialize target
107
+ @target = target
108
+ super
109
+ end
110
+ attr_reader :target
111
+ end
112
+
113
+ #class Regexp
114
+ # alias replaced_triple_equals ===
115
+ # # Allows regular expressions to be compared against numbers in case statements.
116
+ # def === other
117
+ # # This feature is so valuable to the dialplan DSL that a little bit
118
+ # # of hackery is justified.
119
+ #
120
+ # if caller.find {|i| i.ends_with? ":in `run_inside'" } # Test if running in the DSL
121
+ # return replaced_triple_equals(other.to_s) if other.kind_of? Numeric
122
+ # end
123
+ # replaced_triple_equals other
124
+ # end
125
+ #end
126
+
127
+ class Fixnum
128
+
129
+ # One ring is approximately six seconds.
130
+ def rings() self*6 end
131
+ alias second seconds
132
+ alias digit seconds
133
+ alias digits seconds
134
+ def =~ other
135
+ to_s =~ other
136
+ end
137
+
138
+ # Used by __case__ statements. For Adhearsion's purposes, passing
139
+ # a Fixnum to a case statement will convert both the Fixnum operand
140
+ # and other operand to Strings and then return their === equivalency.
141
+ def === other
142
+ to_s === other.to_s
143
+ end
144
+ end
145
+
146
+ class Hash
147
+ def method_missing name, *args
148
+ if name.to_s[-1] == ?= then self[name.to_s.chop.to_sym] = args.first
149
+ else self[name] || self[name.to_s]
150
+ end
151
+ end
152
+ end
153
+
154
+ class Array
155
+ def first=(value) self[0] = value end
156
+ def last=(value) self[-1] = value end
157
+ end