Class: SMS::App
Constant Summary collapse
- NAMED_PRIORITY =
{ :highest => 100, :high => 90, :normal => 50, :low => 10, :lowest => 0 }
Instance Attribute Summary
Attributes inherited from Thing
Class Method Summary collapse
- .method_added(meth) ⇒ Object
-
.priority(priority = nil) ⇒ Object
Sets or returns the priority of this application class.
- .serve(regex) ⇒ Object
-
.serve!(*backends) ⇒ Object
Creates and starts a router to serve only this application.
Instance Method Summary collapse
- #assemble(*parts) ⇒ Object
- #incoming(msg) ⇒ Object
- #message(msg) ⇒ Object
- #priority ⇒ Object
- #priority=(level) ⇒ Object
Methods inherited from Thing
Class Method Details
.method_added(meth) ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/rubysms/application.rb', line 276 def method_added(meth) if @serve @services = []\ unless @services # add this method, along with the last stored # regex, to the map of services for this app. # the default 'incoming' method will iterate # the regexen, and redirect the message to # the method linked here @services.push([meth, @serve]) @serve = nil end end |
.priority(priority = nil) ⇒ Object
Sets or returns the priority of this application class. Returning this value isn’t tremendously useful by itself, and mostly exists for the sake of completeness, and to be called by Application#priority. The value returned is obtained by finding the first ancestor of this class which has a @priority (yes, it looks inside other classes instance variables. I’m sorry.), and converts it to a number via the SMS::App::NAMED_PRIORITY constant.
class One < SMS::App
priority :high
end
class Two < One
end
class Three < Two
priority 36
end
One.priority => 90 # set via NAMED_PRIORITY
Two.priority => 90 # inherited from One
Three.priority => 36 # set literally
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/rubysms/application.rb', line 88 def self.priority(priority=nil) # set the priority of this class if an argument # were provided, and allow execution to continue # to check it's validity unless priority.nil? @priority = priority end # find the first ancestor with a priority # (Class.ancestors *includes* self) self.ancestors.each do |klass| if klass.instance_variable_defined?(:@priority) prio = klass.instance_variable_get(:@priority) # literal numbers are okay, although # that probably isn't such a good idea if prio.is_a?(Numeric) return prio # if this class has a named priority, # resolve and return it's value elsif prio.is_a?(Symbol) if NAMED_PRIORITY.has_key?(prio) return NAMED_PRIORITY[prio] # don't allow invalid named priorites. # i can't think of a use case, especially # since the constant can be monkey-patched # if it's really necessary else raise( NameError, "Invalid named priority #{prio.inspect} " +\ "of {klass}. Valid named priorties are: " +\ NAMED_PRIORITY.keys.join(", ")) end end end end # no ancestor has a priority, so assume # that this app is of "normal" priority return NAMED_PRIORITY[:normal] end |
.serve(regex) ⇒ Object
272 273 274 |
# File 'lib/rubysms/application.rb', line 272 def serve(regex) @serve = regex end |
.serve!(*backends) ⇒ Object
Creates and starts a router to serve only this application. Handy during development.
This method accepts an arbitrary number of backends, each of which can be provided in numerous ways. This is kind of hard to wrap one’s head around, but makes us super flexible. TODO: this magic will all be moved to the
router, one day, so multiple apps
can take advantage of it.
# start the default backends
# (one http, and one drb)
App.serve!
# just the http backend
App.serve!(:HTTP)
# the http backend... with configuration option(s)!
# (in this case, a port). it's got to be an array,
# so we know that we're referring to one single
# backend here, not two "HTTP" and "8080" backends
App.serve!([:HTTP, 8080])
# two GSM backends on separate ports
App.serve!([:GSM, "/dev/ttyS0"], [:GSM, "/dev/ttyS1"])
You may notice that these arguments resemble the config options from the Malawi RapidSMS project… this is not a co-incidence.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/rubysms/application.rb', line 45 def self.serve!(*backends) # if no backends were explicitly requested, # default to the HTTP + DRB offline backends backends = [:HTTP, :DRB] if\ backends.empty? # create a router, and attach each new backend # in turn. because ruby's *splat operator is so # clever, each _backend_ can be provided in many # ways - see this method's docstring. router = SMS::Router.new backends.each do |backend| router.add_backend(*backend) end router.add_app(self.new) router.serve_forever end |
Instance Method Details
#assemble(*parts) ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/rubysms/application.rb', line 225 def assemble(*parts) # the last element can be an array, # which contains arguments to sprintf args = parts[-1].is_a?(Array)? parts.pop : [] # resolve each remaining part # via self#messge, which can # (should?) be overloaded parts.collect do |msg| (msg) end.join("") % args end |
#incoming(msg) ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/rubysms/application.rb', line 142 def incoming(msg) if services = self.class.instance_variable_get(:@services) # duplicate the message text before hacking it # into pieces, so we don't alter the original text = msg.text.dup # lock threads while handling this message, so we don't have # to worry about being interrupted by other incoming messages # (in theory, this shouldn't be a problem, but it turns out # to be a frequent source of bugs) Thread.exclusive do services.each do |service| method, pattern, priority = *service # if the pattern is a string, then assume that # it's a case-insensitive simple trigger - it's # a common enough use-case to warrant an exception if pattern.is_a?(String) pattern = /\A#{pattern}\Z/i end # if this pattern looks like a regex, # attempt to match the incoming message if pattern.respond_to?(:match) if m = pattern.match(text) # we have a match! attempt to # dispatch it to the receiver dispatch_to(method, msg, m.captures) # the method accepted the text, but it may not be interested # in the whole message. so crop off just the part that matched text.sub!(pattern, "") # stop processing if we have # dealt with all of the text return true unless text =~ /\S/ # there is text remaining, so # (re-)start iterating services # (jumps back to services.each) retry end # the special :anything pattern can be used # as a default service. once this is hit, we # are done processing the entire message elsif pattern == :anything dispatch_to(method, msg, [text]) return true # we don't understand what this pattern # is, or how it ended up in @services. # no big deal, but log it anyway, since # it indicates that *something* is awry else log "Invalid pattern: #{pattern.inspect}", :warn end end#each end#exclusive end#if end |
#message(msg) ⇒ Object
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/rubysms/application.rb', line 208 def (msg) if msg.is_a? Symbol begin self.class.const_get(:Messages)[msg] # something went wrong, but i don't # particularly care what, right now. # log it, and carry on regardless rescue StandardError log "Invalid message #{msg.inspect} for #{self.class}", :warn "<#{msg}>" end else msg end end |
#priority ⇒ Object
138 139 140 |
# File 'lib/rubysms/application.rb', line 138 def priority @priority or self.class.priority end |
#priority=(level) ⇒ Object
134 135 136 |
# File 'lib/rubysms/application.rb', line 134 def priority=(level) @priority = level end |