adammck-rubysms 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|