adammck-rubysms 0.8.1
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 +50 -0
- data/bin/rubysms-drb-client +168 -0
- data/lib/drb-client.glade +90 -0
- data/lib/rubysms.rb +39 -0
- data/lib/rubysms/application.rb +207 -0
- data/lib/rubysms/backend.rb +61 -0
- data/lib/rubysms/backend/cellphone.ico +0 -0
- data/lib/rubysms/backend/drb.rb +49 -0
- data/lib/rubysms/backend/gsm.rb +116 -0
- data/lib/rubysms/backend/http.rb +414 -0
- data/lib/rubysms/errors.rb +17 -0
- data/lib/rubysms/logger.rb +69 -0
- data/lib/rubysms/message/incoming.rb +58 -0
- data/lib/rubysms/message/outgoing.rb +40 -0
- data/lib/rubysms/person.rb +47 -0
- data/lib/rubysms/router.rb +198 -0
- data/lib/rubysms/thing.rb +38 -0
- data/rubysms.gemspec +47 -0
- metadata +99 -0
data/README.rdoc
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
RubySMS is a Ruby library (not a framework, although it's coming dangerously close)
|
2
|
+
which aims to make developing and deploying SMS applications easy. The sending and
|
3
|
+
receiving of "real" SMS is handled by RubyGSM[http://github.com/adammck/rubygsm],
|
4
|
+
but RubySMS also provides a couple of ways to interact with "mock" SMS via a web
|
5
|
+
interface (the HTTP Backend) and GTK GUI (the DRB backend).
|
6
|
+
|
7
|
+
RubySMS was initially developed in Malawi by {The UNICEF Innovation Team}[http://unicefinnovation.org/about.php]
|
8
|
+
and a group from Columbia University's {SIPA}[http://sipa.columbia.edu/] as the technical foundation of the
|
9
|
+
{RapidSMS Child Malnutrition Survelliance}[http://netsquared.org/projects/child-malnutrition-surveillance-and-famine-response]
|
10
|
+
(see {the source code}[http://github.com/adammck/columbawawi]) entry to the {USAID Development 2.0 Challenge}[http://netsquared.org/usaid]...
|
11
|
+
which[http://globaldevelopmentcommons.net/node/876]
|
12
|
+
we[http://unicef.org/infobycountry/usa_47068.html]
|
13
|
+
won[http://sipa.columbia.edu/news_events/announcements/sipanews10.html].
|
14
|
+
|
15
|
+
|
16
|
+
=== Sample Application
|
17
|
+
|
18
|
+
require "rubygems"
|
19
|
+
require "rubysms"
|
20
|
+
|
21
|
+
class DemoApp < SMS::App
|
22
|
+
def incoming(msg)
|
23
|
+
msg.respond("Wow, that was easy!")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
DemoApp.serve!
|
28
|
+
|
29
|
+
|
30
|
+
=== Installing
|
31
|
+
RubySMS is distributed via GitHub[http://github.com/adammck/rubysms], which you must
|
32
|
+
add as a Gem source before installing:
|
33
|
+
|
34
|
+
$ sudo gem sources -a http://gems.github.com
|
35
|
+
|
36
|
+
Then install the Gem, which will automatically pull in the dependencies:
|
37
|
+
|
38
|
+
$ sudo gem install adammck-rubysms
|
39
|
+
|
40
|
+
|
41
|
+
=== Dependencies
|
42
|
+
If you'd prefer to run RubySMS from the trunk, you'll need to install the following
|
43
|
+
Gems manually (otherwise, the HTTP backend will explode on startup. Remind me to fix
|
44
|
+
that some time).
|
45
|
+
|
46
|
+
$ sudo gem install rack mongrel
|
47
|
+
|
48
|
+
If you'd like to send real SMS via a GSM modem, you'll also need RubyGSM:
|
49
|
+
|
50
|
+
$ sudo gem install adammck-rubygsm
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "libglade2.rb"
|
4
|
+
require "drb.rb"
|
5
|
+
|
6
|
+
SERVER_PORT = "1370"
|
7
|
+
|
8
|
+
class SmsGui
|
9
|
+
include GetText
|
10
|
+
attr :glade
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
|
14
|
+
# if this file is a symlink (which is probably is, since rubygems
|
15
|
+
# automatically symlinks bin files into /usr/bin), find the original
|
16
|
+
# source file, so we can find the glade src relatively
|
17
|
+
this_file = File.symlink?(__FILE__) ? \
|
18
|
+
File.readlink(__FILE__) : __FILE__
|
19
|
+
|
20
|
+
# find and load up the glade src for the gui
|
21
|
+
glade_src = File.expand_path(File.dirname(this_file) + "/../lib/drb-client.glade")
|
22
|
+
@glade = GladeXML.new(glade_src) do |handler|
|
23
|
+
method(handler)
|
24
|
+
end
|
25
|
+
|
26
|
+
# for storing outgoing
|
27
|
+
# messages for recall
|
28
|
+
@history = []
|
29
|
+
@hist_pos = 0
|
30
|
+
|
31
|
+
# fetch references to the gtk widgets
|
32
|
+
@source = @glade.get_widget("entry_source")
|
33
|
+
@entry = @glade.get_widget("entry_message")
|
34
|
+
@log = @glade.get_widget("textview_log")
|
35
|
+
@lb = @log.buffer
|
36
|
+
|
37
|
+
# create a text mark to keep at the end of
|
38
|
+
# the log, so we can keep scrolling to it
|
39
|
+
@lb.create_mark("end", @lb.end_iter, true)
|
40
|
+
|
41
|
+
# create colors for incoming and outgoing messages
|
42
|
+
@lb.create_tag("incoming", "family"=>"monospace", "foreground"=>"#AA0000")
|
43
|
+
@lb.create_tag("outgoing", "family"=>"monospace", "foreground"=>"#0000AA")
|
44
|
+
@lb.create_tag("error", "family"=>"monospace", "foreground"=>"#FFFFFF", "background"=>"#FF0000")
|
45
|
+
|
46
|
+
# prepopulate the source phone number with six digits
|
47
|
+
@src = (1111 + rand(8888)).to_s
|
48
|
+
@source.text = @src
|
49
|
+
|
50
|
+
# fire up drb, to send outgoing messages to rubysms
|
51
|
+
@injector = DRbObject.new_with_uri("druby://localhost:#{SERVER_PORT}")
|
52
|
+
puts "Connected to RubySMS at: #{@injector.__drburi}"
|
53
|
+
|
54
|
+
# start listening for incoming messages from rubysms
|
55
|
+
@drb = DRb.start_service("druby://localhost:#{SERVER_PORT}#{@src}", self)
|
56
|
+
puts "Started DRb client at: #{@drb.uri}"
|
57
|
+
|
58
|
+
# display the GUI
|
59
|
+
@glade.get_widget("window").show
|
60
|
+
@entry.grab_focus
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# user closed the window,
|
65
|
+
# so terminate the program
|
66
|
+
def on_quit
|
67
|
+
Gtk.main_quit
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# user clicked "send", or pressed
|
72
|
+
# enter while in the entry field
|
73
|
+
def on_button_send_clicked
|
74
|
+
msg = @entry.text
|
75
|
+
|
76
|
+
# do nothing if the message is blank
|
77
|
+
return nil if msg.empty?
|
78
|
+
log ">> #{msg}", "incoming"
|
79
|
+
|
80
|
+
begin
|
81
|
+
# attempt to send the message
|
82
|
+
# to rubysms via drb, as if it
|
83
|
+
# were a real incoming sms
|
84
|
+
@injector.incoming(@src, msg)
|
85
|
+
|
86
|
+
@hist_pos = 0
|
87
|
+
@history.push(msg)
|
88
|
+
@entry.text = ""
|
89
|
+
|
90
|
+
# couldn't connect!
|
91
|
+
rescue DRb::DRbConnError
|
92
|
+
log("Connection to RubySMS failed!", "error")
|
93
|
+
#log("Tried URI: #{@injector.__drburi}", "error")
|
94
|
+
rescue => err
|
95
|
+
log(err.message, "error")
|
96
|
+
log(" " + err.backtrace.join("\n "), "error")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_entry_message_key_press(widget, event)
|
101
|
+
if event.keyval == 65362 # up arrow
|
102
|
+
if @hist_pos > -@history.length
|
103
|
+
@hist_pos -= 1
|
104
|
+
@entry.text = @history[@hist_pos]
|
105
|
+
@entry.grab_focus
|
106
|
+
end
|
107
|
+
|
108
|
+
# prevent default event,
|
109
|
+
# which bumps the focus
|
110
|
+
# upwards to the log
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
|
114
|
+
if event.keyval == 65364 # down arrow
|
115
|
+
if @hist_pos < -1
|
116
|
+
@hist_pos += 1
|
117
|
+
@entry.text = @history[@hist_pos]
|
118
|
+
@entry.grab_focus
|
119
|
+
end
|
120
|
+
|
121
|
+
# prevent default
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
|
125
|
+
# any other key resets
|
126
|
+
# the position in history
|
127
|
+
@hist_pos = 0
|
128
|
+
return false
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def log(msg, tag)
|
133
|
+
# prepend a newline, unless this
|
134
|
+
# is the first entry (to avoid a
|
135
|
+
# one-line gap at the top)
|
136
|
+
msg = "\n" + msg\
|
137
|
+
if @lb.char_count > 0
|
138
|
+
|
139
|
+
# add a single entry to the message log
|
140
|
+
# (using the colors defined in #initialize)
|
141
|
+
@lb.insert(@lb.end_iter, msg, tag)
|
142
|
+
|
143
|
+
# scroll to the end of the *previous*
|
144
|
+
# message, to bring the top of this
|
145
|
+
# message into view, in case it is long
|
146
|
+
@log.scroll_to_mark(@lb.get_mark("end"), 0, true, 1, 0)
|
147
|
+
|
148
|
+
# update the position of the end marker,
|
149
|
+
# which we will scroll to (above), when
|
150
|
+
# the next message is logged
|
151
|
+
@lb.move_mark(@lb.get_mark("end"), @lb.end_iter)
|
152
|
+
@lb.end_iter.line_offset = 0
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def incoming(msg)
|
157
|
+
# we received a message! do nothing
|
158
|
+
# except add it to the message log
|
159
|
+
msg.gsub!(/\n/, "\n ")
|
160
|
+
log "<< #{msg}", "outgoing"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
# initialize, and block
|
166
|
+
# until GTK terminates
|
167
|
+
gui = SmsGui.new
|
168
|
+
Gtk.main
|
@@ -0,0 +1,90 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<glade-interface>
|
3
|
+
<!--<requires-version lib="gtk+" version="2.12"/>-->
|
4
|
+
<widget class="GtkWindow" id="window">
|
5
|
+
<property name="border_width">6</property>
|
6
|
+
<property name="title" translatable="yes">RubySMS Virtual Device</property>
|
7
|
+
<property name="default_width">500</property>
|
8
|
+
<property name="default_height">300</property>
|
9
|
+
<property name="icon_name">mail-send-receive</property>
|
10
|
+
<signal name="delete_event" handler="on_quit"/>
|
11
|
+
<child>
|
12
|
+
<widget class="GtkVBox" id="vbox1">
|
13
|
+
<property name="visible">True</property>
|
14
|
+
<property name="spacing">6</property>
|
15
|
+
<child>
|
16
|
+
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
17
|
+
<property name="visible">True</property>
|
18
|
+
<property name="can_focus">True</property>
|
19
|
+
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
20
|
+
<property name="shadow_type">GTK_SHADOW_IN</property>
|
21
|
+
<child>
|
22
|
+
<widget class="GtkTextView" id="textview_log">
|
23
|
+
<property name="visible">True</property>
|
24
|
+
<property name="can_focus">True</property>
|
25
|
+
<property name="events"></property>
|
26
|
+
<property name="editable">False</property>
|
27
|
+
</widget>
|
28
|
+
</child>
|
29
|
+
</widget>
|
30
|
+
</child>
|
31
|
+
<child>
|
32
|
+
<widget class="GtkTable" id="table2">
|
33
|
+
<property name="visible">True</property>
|
34
|
+
<property name="n_columns">3</property>
|
35
|
+
<property name="column_spacing">6</property>
|
36
|
+
<child>
|
37
|
+
<widget class="GtkEntry" id="entry_message">
|
38
|
+
<property name="visible">True</property>
|
39
|
+
<property name="can_focus">True</property>
|
40
|
+
<property name="has_tooltip">True</property>
|
41
|
+
<property name="tooltip" translatable="yes">Message text</property>
|
42
|
+
<property name="activates_default">True</property>
|
43
|
+
<signal name="key_press_event" handler="on_entry_message_key_press"/>
|
44
|
+
<accelerator key="T" signal="grab-focus" modifiers="GDK_MOD1_MASK"/>
|
45
|
+
</widget>
|
46
|
+
<packing>
|
47
|
+
<property name="left_attach">1</property>
|
48
|
+
<property name="right_attach">2</property>
|
49
|
+
<property name="y_options">GTK_FILL</property>
|
50
|
+
</packing>
|
51
|
+
</child>
|
52
|
+
<child>
|
53
|
+
<widget class="GtkButton" id="button_send">
|
54
|
+
<property name="visible">True</property>
|
55
|
+
<property name="can_focus">True</property>
|
56
|
+
<property name="can_default">True</property>
|
57
|
+
<property name="has_default">True</property>
|
58
|
+
<property name="receives_default">True</property>
|
59
|
+
<property name="label" translatable="yes">Send SMS</property>
|
60
|
+
<property name="response_id">0</property>
|
61
|
+
<signal name="clicked" handler="on_button_send_clicked"/>
|
62
|
+
</widget>
|
63
|
+
<packing>
|
64
|
+
<property name="left_attach">2</property>
|
65
|
+
<property name="right_attach">3</property>
|
66
|
+
<property name="x_options">GTK_FILL</property>
|
67
|
+
</packing>
|
68
|
+
</child>
|
69
|
+
<child>
|
70
|
+
<widget class="GtkEntry" id="entry_source">
|
71
|
+
<property name="width_request">60</property>
|
72
|
+
<property name="visible">True</property>
|
73
|
+
<property name="can_focus">True</property>
|
74
|
+
<property name="tooltip" translatable="yes">DRb TCP/IP Port</property>
|
75
|
+
<property name="editable">False</property>
|
76
|
+
</widget>
|
77
|
+
<packing>
|
78
|
+
<property name="x_options">GTK_FILL</property>
|
79
|
+
</packing>
|
80
|
+
</child>
|
81
|
+
</widget>
|
82
|
+
<packing>
|
83
|
+
<property name="expand">False</property>
|
84
|
+
<property name="position">1</property>
|
85
|
+
</packing>
|
86
|
+
</child>
|
87
|
+
</widget>
|
88
|
+
</child>
|
89
|
+
</widget>
|
90
|
+
</glade-interface>
|
data/lib/rubysms.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#:title:RubySMS
|
3
|
+
#--
|
4
|
+
# vim: noet
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
# during development, it's important to EXPLODE
|
9
|
+
# as early as possible when something goes wrong
|
10
|
+
Thread.abort_on_exception = true
|
11
|
+
Thread.current["name"] = "main"
|
12
|
+
|
13
|
+
|
14
|
+
# everything (should) live
|
15
|
+
# in this tidy namespace
|
16
|
+
module SMS
|
17
|
+
|
18
|
+
# store this directory name; everything
|
19
|
+
# inside here is considered to be part
|
20
|
+
# of the rubysms framework, so can be
|
21
|
+
# ignored in application backtraces
|
22
|
+
Root = File.dirname(__FILE__)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# load all supporting files
|
27
|
+
dir = SMS::Root + "/rubysms"
|
28
|
+
require "#{dir}/logger.rb"
|
29
|
+
require "#{dir}/router.rb"
|
30
|
+
require "#{dir}/thing.rb"
|
31
|
+
require "#{dir}/application.rb"
|
32
|
+
require "#{dir}/backend.rb"
|
33
|
+
require "#{dir}/errors.rb"
|
34
|
+
require "#{dir}/person.rb"
|
35
|
+
|
36
|
+
|
37
|
+
# message classes
|
38
|
+
require "#{dir}/message/incoming.rb"
|
39
|
+
require "#{dir}/message/outgoing.rb"
|
@@ -0,0 +1,207 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: noet
|
3
|
+
|
4
|
+
module SMS
|
5
|
+
class App < Thing
|
6
|
+
|
7
|
+
# Creates and starts a router to serve only
|
8
|
+
# this application. Handy during development.
|
9
|
+
#
|
10
|
+
# This method accepts an arbitrary number of
|
11
|
+
# backends, each of which can be provided in
|
12
|
+
# numerous ways. This is kind of hard to wrap
|
13
|
+
# one's head around, but makes us super flexible.
|
14
|
+
# TODO: this magic will all be moved to the
|
15
|
+
# router, one day, so multiple apps
|
16
|
+
# can take advantage of it.
|
17
|
+
#
|
18
|
+
# # start the default backends
|
19
|
+
# # (one http, and one drb)
|
20
|
+
# App.serve!
|
21
|
+
#
|
22
|
+
# # just the http backend
|
23
|
+
# App.serve!(:HTTP)
|
24
|
+
#
|
25
|
+
# # the http backend... with configuration option(s)!
|
26
|
+
# # (in this case, a port). it's got to be an array,
|
27
|
+
# # so we know that we're referring to one single
|
28
|
+
# # backend here, not two "HTTP" and "8080" backends
|
29
|
+
# App.serve!([:HTTP, 8080])
|
30
|
+
#
|
31
|
+
# # two GSM backends on separate ports
|
32
|
+
# App.serve!([:GSM, "/dev/ttyS0"], [:GSM, "/dev/ttyS1"])
|
33
|
+
#
|
34
|
+
# You may notice that these arguments resemble the
|
35
|
+
# config options from the Malawi RapidSMS project...
|
36
|
+
# this is not a co-incidence.
|
37
|
+
def self.serve!(*backends)
|
38
|
+
|
39
|
+
# if no backends were explicitly requested,
|
40
|
+
# default to the HTTP + DRB offline backends
|
41
|
+
backends = [:HTTP, :DRB] if\
|
42
|
+
backends.empty?
|
43
|
+
|
44
|
+
# create a router, and attach each new backend
|
45
|
+
# in turn. because ruby's *splat operator is so
|
46
|
+
# clever, each _backend_ can be provided in many
|
47
|
+
# ways - see this method's docstring.
|
48
|
+
router = SMS::Router.new
|
49
|
+
backends.each do |backend|
|
50
|
+
router.add_backend(*backend)
|
51
|
+
end
|
52
|
+
|
53
|
+
router.add_app(self.new)
|
54
|
+
router.serve_forever
|
55
|
+
end
|
56
|
+
|
57
|
+
def incoming(msg)
|
58
|
+
if services = self.class.instance_variable_get(:@services)
|
59
|
+
|
60
|
+
# duplicate the message text before hacking it
|
61
|
+
# into pieces, so we don't alter the original
|
62
|
+
text = msg.text.dup
|
63
|
+
|
64
|
+
# lock threads while handling this message, so we don't have
|
65
|
+
# to worry about being interrupted by other incoming messages
|
66
|
+
# (in theory, this shouldn't be a problem, but it turns out
|
67
|
+
# to be a frequent source of bugs)
|
68
|
+
Thread.exclusive do
|
69
|
+
services.each do |service|
|
70
|
+
method, pattern, priority = *service
|
71
|
+
|
72
|
+
# if the pattern is a string, then assume that
|
73
|
+
# it's a case-insensitive simple trigger - it's
|
74
|
+
# a common enough use-case to warrant an exception
|
75
|
+
if pattern.is_a?(String)
|
76
|
+
pattern = /\A#{pattern}\Z/i
|
77
|
+
end
|
78
|
+
|
79
|
+
# if this pattern looks like a regex,
|
80
|
+
# attempt to match the incoming message
|
81
|
+
if pattern.respond_to?(:match)
|
82
|
+
if m = pattern.match(text)
|
83
|
+
|
84
|
+
# we have a match! attempt to
|
85
|
+
# dispatch it to the receiver
|
86
|
+
dispatch_to(method, msg, m.captures)
|
87
|
+
|
88
|
+
# the method accepted the text, but it may not be interested
|
89
|
+
# in the whole message. so crop off just the part that matched
|
90
|
+
text.sub!(pattern, "")
|
91
|
+
|
92
|
+
# stop processing if we have
|
93
|
+
# dealt with all of the text
|
94
|
+
return true unless text =~ /\S/
|
95
|
+
|
96
|
+
# there is text remaining, so
|
97
|
+
# (re-)start iterating services
|
98
|
+
# (jumps back to services.each)
|
99
|
+
retry
|
100
|
+
end
|
101
|
+
|
102
|
+
# the special :anything pattern can be used
|
103
|
+
# as a default service. once this is hit, we
|
104
|
+
# are done processing the entire message
|
105
|
+
elsif pattern == :anything
|
106
|
+
dispatch_to(method, msg, [text])
|
107
|
+
return true
|
108
|
+
|
109
|
+
# we don't understand what this pattern
|
110
|
+
# is, or how it ended up in @services.
|
111
|
+
# no big deal, but log it anyway, since
|
112
|
+
# it indicates that *something* is awry
|
113
|
+
else
|
114
|
+
log "Invalid pattern: #{pattern.inspect}", :warn
|
115
|
+
end
|
116
|
+
end#each
|
117
|
+
|
118
|
+
|
119
|
+
end#exclusive
|
120
|
+
end#if
|
121
|
+
end
|
122
|
+
|
123
|
+
def message(msg)
|
124
|
+
if msg.is_a? Symbol
|
125
|
+
begin
|
126
|
+
self.class.const_get(:Messages)[msg]
|
127
|
+
|
128
|
+
# something went wrong, but i don't
|
129
|
+
# particularly care what, right now.
|
130
|
+
# log it, and carry on regardless
|
131
|
+
rescue StandardError
|
132
|
+
log "Invalid message #{msg.inspect} for #{self.class}", :warn
|
133
|
+
"<#{msg}>"
|
134
|
+
end
|
135
|
+
else
|
136
|
+
msg
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def assemble(*parts)
|
141
|
+
|
142
|
+
# the last element can be an array,
|
143
|
+
# which contains arguments to sprintf
|
144
|
+
args = parts[-1].is_a?(Array)? parts.pop : []
|
145
|
+
|
146
|
+
# resolve each remaining part
|
147
|
+
# via self#messge, which can
|
148
|
+
# (should?) be overloaded
|
149
|
+
parts.collect do |msg|
|
150
|
+
message(msg)
|
151
|
+
end.join("") % args
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def dispatch_to(meth_str, msg, captures)
|
157
|
+
log_dispatch(meth_str, captures)
|
158
|
+
|
159
|
+
begin
|
160
|
+
err_line = __LINE__ + 1
|
161
|
+
send(meth_str, msg, *captures)
|
162
|
+
|
163
|
+
rescue ArgumentError => err
|
164
|
+
|
165
|
+
# if the line above (where we dispatch to the receiving
|
166
|
+
# method) caused the error, we'll log a more useful message
|
167
|
+
if (err.backtrace[0] =~ /^#{__FILE__}:#{err_line}/)
|
168
|
+
wanted = (method(meth_str).arity - 1)
|
169
|
+
problem = (captures.length > wanted) ? "Too many" : "Not enough"
|
170
|
+
log "#{problem} captures (wanted #{wanted}, got #{captures.length})", :warn
|
171
|
+
|
172
|
+
else
|
173
|
+
raise
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Adds a log message detailing which method is being
|
179
|
+
# invoked, with which arguments (if any)
|
180
|
+
def log_dispatch(method, args=[])
|
181
|
+
meth_str = self.class.to_s + "#" + method.to_s
|
182
|
+
meth_str += " #{args.inspect}" unless args.empty?
|
183
|
+
log "Dispatching to: #{meth_str}"
|
184
|
+
end
|
185
|
+
|
186
|
+
class << self
|
187
|
+
def serve(regex)
|
188
|
+
@serve = regex
|
189
|
+
end
|
190
|
+
|
191
|
+
def method_added(meth)
|
192
|
+
if @serve
|
193
|
+
@services = []\
|
194
|
+
unless @services
|
195
|
+
|
196
|
+
# add this method, along with the last stored
|
197
|
+
# regex, to the map of services for this app.
|
198
|
+
# the default 'incoming' method will iterate
|
199
|
+
# the regexen, and redirect the message to
|
200
|
+
# the method linked here
|
201
|
+
@services.push([meth, @serve])
|
202
|
+
@serve = nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end # App
|
207
|
+
end # SMS
|