Skip to content
Browse files

time to put this stuff on the hub

  • Loading branch information...
0 parents commit 3f76758c0391a1bce0256b4c78589b472c4fd735 @benburkert committed Oct 2, 2008
Showing with 804 additions and 0 deletions.
  1. +33 −0 README.textile
  2. +34 −0 emp-core/lib/emp-core.rb
  3. +39 −0 emp-core/lib/emp-core/autoload.rb
  4. +40 −0 emp-core/lib/emp-core/bootloader.rb
  5. +7 −0 emp-core/lib/emp-core/component/emp_component.rb
  6. +5 −0 emp-core/lib/emp-core/core_ext/array.rb
  7. +6 −0 emp-core/lib/emp-core/core_ext/object.rb
  8. +5 −0 emp-core/lib/emp-core/core_ext/string.rb
  9. +34 −0 emp-core/lib/emp-core/dispatcher.rb
  10. +25 −0 emp-core/lib/emp-core/event_machine/handler.rb
  11. +37 −0 emp-core/lib/emp-core/jid/jid.rb
  12. +46 −0 emp-core/lib/emp-core/libxml/handler.rb
  13. +26 −0 emp-core/lib/emp-core/libxml_ext/node.rb
  14. +19 −0 emp-core/lib/emp-core/libxml_ext/node_map.rb
  15. +20 −0 emp-core/lib/emp-core/nodes/auth.rb
  16. +22 −0 emp-core/lib/emp-core/nodes/bind.rb
  17. +5 −0 emp-core/lib/emp-core/nodes/body.rb
  18. +10 −0 emp-core/lib/emp-core/nodes/challenge.rb
  19. +8 −0 emp-core/lib/emp-core/nodes/doctype.rb
  20. +6 −0 emp-core/lib/emp-core/nodes/jid_node.rb
  21. +12 −0 emp-core/lib/emp-core/nodes/mechanism.rb
  22. +10 −0 emp-core/lib/emp-core/nodes/mechanisms.rb
  23. +13 −0 emp-core/lib/emp-core/nodes/node.rb
  24. +10 −0 emp-core/lib/emp-core/nodes/register.rb
  25. +5 −0 emp-core/lib/emp-core/nodes/resource.rb
  26. +9 −0 emp-core/lib/emp-core/nodes/session.rb
  27. +23 −0 emp-core/lib/emp-core/nodes/stream.rb
  28. +10 −0 emp-core/lib/emp-core/nodes/stream_features.rb
  29. +10 −0 emp-core/lib/emp-core/nodes/success.rb
  30. +10 −0 emp-core/lib/emp-core/post_init.rb
  31. +13 −0 emp-core/lib/emp-core/route/route.rb
  32. +32 −0 emp-core/lib/emp-core/route/router.rb
  33. +9 −0 emp-core/lib/emp-core/stanzas/info_query.rb
  34. +5 −0 emp-core/lib/emp-core/stanzas/message.rb
  35. +14 −0 emp-core/lib/emp-core/stanzas/presence.rb
  36. +4 −0 emp-core/lib/emp-core/stanzas/stanza.rb
  37. +38 −0 emp-core/spec/emp-core/unit/event_machine/handler_spec.rb
  38. +26 −0 emp-core/spec/emp-core/unit/libxml/handler_spec.rb
  39. +6 −0 emp-core/spec/spec_helper.rb
  40. +24 −0 emp-examples/hack/hack.rb
  41. +94 −0 emp-examples/tweeter/tweeter.rb
33 README.textile
@@ -0,0 +1,33 @@
+h1. EMP
+
+**EMP** = **E**vent **M**achine + X**MP**P
+
+h2. About
+
+**Warning! This is a proof of concept hack. It needs to be rewritten at least 2 or 3 more times.**
+
+xmpp4r sucks, it's the equivalent of CGI scripts for the internets. The "thing to do" with xmpp4r is to write bots. Why? Because bots are like simple little CGI scripts. Trying to write anything complex on top of xmpp4r will cause much pain and suffering, hair pulling, and resentment towards XMPP. What we need is a framework that makes working with XMPP as enjoyable as Rails made HTTP.
+
+Turns out merb could be a great meta-framework: a framework for writing other frameworks. It's got great support for setting up a damon process, defining a boot sequence, logging, routing, generating/templates, and more. EMP has been poaching stuff from merb and follows the same basic layout.
+
+h2. How It Works
+
+EMP uses Event Machine to connect to an XMPP server. In the equivalent of the merb's init file, you specify the node, host, authentication method, and password or shared key. In the equivalent to merb's router file, you specify the resources your app 'serves', along with the code to define routes from raw stanzas to the components & actions (equivalent to merb's controllers & actions), which would probably defined in app/components/. A separate event machine connection is established per resource (which corresponds to a JID).
+
+The boot sequence should mirror merb's boot sequence. The difference being instead of the "rack up" step, the event machine connections are setup, and the xmpp chatter to establish authentication, resource binding, and an initial presence are additional steps. Once the bootup sequence is done, your left with a damon waiting on XMPP requests.
+
+When EM receives data (e.g. an XMPP request), it passes it off to a libxml SAX parser. The SAX parser builds the XMPP Nodes, and as soon as enough of information is received to handle a request, it's passed off to the Dispatcher. The Dispatcher builds XPath queries based on the route table. If it finds a match, it passes the XMPP payload to Component and dispatches the action. Same as merb, but with XML stanzas instead of http requests. Also, this should happen inside an EventMachine.defer call to protect against actions that block. The response from the action is passed back to the EM handler and sent out on the wire.
+
+h2. TODO
+
+* Figure out why it's stalling after authentication.
+* Tie in PubSub somehow.
+* Use the XEP-0114: Jabber Component Protocol by default (http://xmpp.org/extensions/xep-0114.html).
+* Rework the boot sequence to handle "encryption, authentication, and resource binding", as well as the presence stuff.
+* Add a router based on XPath.
+* Handle dispatches to the Components using the EventMachine.defer method.
+* Burn all this code and start over again.
+
+h3. Examples
+
+There are two examples under the emp-examples dir: hack/hack.rb and tweeter/tweeter.rb. "Hack" is the most basic EMP example, and doesn't work. Neither does "Tweeter", but this example shows how an API could be implemented with EMP.
34 emp-core/lib/emp-core.rb
@@ -0,0 +1,34 @@
+require 'rubygems'
+
+require 'base64'
+require 'extlib'
+require 'eventmachine'
+require 'libxml'
+
+
+module Emp; end
+
+dir = File.dirname(__FILE__) / 'emp-core'
+
+require dir / :autoload
+
+module Emp
+ class << self
+ attr_accessor :node, :host, :port, :passwd
+ end
+
+ def self.setup(identity, port = 5222, passwd = nil)
+ self.node, self.host = *identity.split('@')
+ self.port, self.passwd = port, passwd
+ end
+
+ def self.start
+ Emp::Bootloader.run
+
+ EventMachine::run do
+ Emp::Jid.jid_list.each do |jid|
+ EventMachine::connect Emp.host, Emp.port, Emp::XmppHandler, [jid, Emp::PostInit.callback_map[jid]]
+ end
+ end
+ end
+end
39 emp-core/lib/emp-core/autoload.rb
@@ -0,0 +1,39 @@
+module Emp
+ autoload :Bootloader, 'emp-core' / :bootloader
+ autoload :Dispatcher, 'emp-core' / :dispatcher
+ autoload :EmpComponent, 'emp-core' / :component / :emp_component
+ autoload :XmppHandler, 'emp-core' / :event_machine / :handler
+ autoload :Jid, 'emp-core' / :jid / :jid
+ autoload :PostInit, 'emp-core' / :post_init
+ autoload :Route, 'emp-core' / :route / :route
+ autoload :Router, 'emp-core' / :route / :router
+ autoload :XmlHandler, 'emp-core' / :libxml / :handler
+end
+
+require 'emp-core' / :core_ext / :array
+require 'emp-core' / :core_ext / :object
+require 'emp-core' / :core_ext / :string
+
+require 'emp-core' / :libxml_ext / :node
+require 'emp-core' / :libxml_ext / :node_map
+
+require 'emp-core' / :nodes / :node
+require 'emp-core' / :nodes / :auth
+require 'emp-core' / :nodes / :bind
+require 'emp-core' / :nodes / :body
+require 'emp-core' / :nodes / :challenge
+require 'emp-core' / :nodes / :doctype
+require 'emp-core' / :nodes / :jid_node
+require 'emp-core' / :nodes / :mechanism
+require 'emp-core' / :nodes / :mechanisms
+require 'emp-core' / :nodes / :register
+require 'emp-core' / :nodes / :resource
+require 'emp-core' / :nodes / :session
+require 'emp-core' / :nodes / :stream
+require 'emp-core' / :nodes / :stream_features
+require 'emp-core' / :nodes / :success
+
+require 'emp-core' / :stanzas / :stanza
+require 'emp-core' / :stanzas / :info_query
+require 'emp-core' / :stanzas / :message
+require 'emp-core' / :stanzas / :presence
40 emp-core/lib/emp-core/bootloader.rb
@@ -0,0 +1,40 @@
+module Emp
+ class Bootloader
+ cattr_accessor :subclasses, :after_load_callbacks, :before_load_callbacks, :finished
+ self.subclasses, self.after_load_callbacks, self.before_load_callbacks, self.finished = [], [], [], []
+
+ def self.inherited(klass)
+ subclasses << klass.to_s
+ super
+ end
+
+ def self.run
+ subklasses = subclasses.dup
+ until subclasses.empty?
+ bootloader = subclasses.shift
+
+ Object.full_const_get(bootloader).run
+ self.finished << bootloader
+ end
+ self.subclasses = subklasses
+ end
+ end
+end
+
+# TODO: figure out a real boot sequence that will handle "encryption, authentication, and resource binding", as well as the presence stuff
+
+class Emp::Bootloader::CompileRoutes < Emp::Bootloader
+ def self.run
+ Emp::Router.run
+ end
+end
+
+class Emp::Bootloader::WireDefaultCallbacks < Emp::Bootloader
+ def self.run
+ Emp::Jid.jid_list.each do |jid|
+ Emp::PostInit.add_callback(jid) do |handler|
+ handler.send_data([Emp::Doctype, Emp::Stream.to(jid.host)])
+ end
+ end
+ end
+end
7 emp-core/lib/emp-core/component/emp_component.rb
@@ -0,0 +1,7 @@
+module Emp
+ class EmpComponent
+ def initialize(request, jid)
+ @request, @jid = request, jid
+ end
+ end
+end
5 emp-core/lib/emp-core/core_ext/array.rb
@@ -0,0 +1,5 @@
+class Array
+ def to_xml
+ map {|e| e.to_xml}.join
+ end
+end
6 emp-core/lib/emp-core/core_ext/object.rb
@@ -0,0 +1,6 @@
+class Object
+ def args_and_opts(*args)
+ options = Hash === args.last ? args.pop : {}
+ return args, options
+ end
+end
5 emp-core/lib/emp-core/core_ext/string.rb
@@ -0,0 +1,5 @@
+class String
+ def to_xml
+ self
+ end
+end
34 emp-core/lib/emp-core/dispatcher.rb
@@ -0,0 +1,34 @@
+module Emp
+ class Dispatcher
+ class << self
+ attr_accessor :route_table
+ end
+
+ self.route_table = []
+
+ def self.add_route(route)
+ route_table << route
+ route
+ end
+
+ # TODO: this is a huge f'in hack to handle the "encryption, authentication, and resource binding",
+ # and it belongs in the Bootloader sequence, not the dispatcher.
+ def self.handle(node_or_stanza, jid)
+ if node_or_stanza.is_a? Stanza
+ if node_or_stanza.is_a? InfoQuery
+ set_presence(jid)
+ end
+ else
+ handle_node(node_or_stanza, jid)
+ end
+ end
+
+ def self.handle_node(node, jid)
+ node.handle(jid)
+ end
+
+ def self.set_presence(jid)
+ jid.xmpp_handler.send_data(Presence.initial(jid))
+ end
+ end
+end
25 emp-core/lib/emp-core/event_machine/handler.rb
@@ -0,0 +1,25 @@
+module Emp
+ class XmppHandler < EventMachine::Connection
+ def initialize(jid_and_procs)
+ @jid, @post_init_procs = *jid_and_procs
+ @jid.xmpp_handler = self
+ @xml_handler = Emp::XmlHandler.new(@jid)
+
+ super
+ end
+
+ def post_init
+ @post_init_procs.each {|proc| proc.call(self) }
+ end
+
+ def send_data(data)
+ p "CLIENT: #{data.to_xml}"
+ super data.to_xml
+ end
+
+ def receive_data(data)
+ p "SERVER: #{data}"
+ @xml_handler.parse(data)
+ end
+ end
+end
37 emp-core/lib/emp-core/jid/jid.rb
@@ -0,0 +1,37 @@
+module Emp
+ #TODO: this class has tacked on too much functionality, figure out a less fuzzy approach
+ class Jid
+ class << self
+ attr_accessor :jid_table, :jid_list
+ end
+
+ self.jid_table = Hash.new {|h, host| h[host] = Hash.new {|h, node| h[node] = {}}}
+ self.jid_list = []
+
+ attr_accessor :node, :host, :passwd, :resource, :xmpp_handler, :xml_handler
+
+ def self.parse(uri, passwd = nil)
+ #...
+ end
+
+ def self.default
+ @default ||= self.new(Emp.node, Emp.host, '', Emp.passwd)
+ end
+
+ def self.find_or_create(node, host, resource, passwd = nil)
+ self.jid_table[node][host][resource] ||= self.new(node, host, resource, passwd)
+ end
+
+ def initialize(node, host, resource, passwd = nil)
+ @node, @host, @passwd, @resource = node, host, passwd, resource
+ self.class.jid_table[host][node][resource] = self
+ self.class.jid_list << self
+ end
+
+ def to_s
+ s = "#{node}@#{host}"
+ s << "/#{resource}" unless resource.blank?
+ s
+ end
+ end
+end
46 emp-core/lib/emp-core/libxml/handler.rb
@@ -0,0 +1,46 @@
+module Emp
+ class XmlHandler
+ include LibXML::XML::SaxParser::Callbacks
+
+ def initialize(jid)
+ @jid = jid
+ @jid.xml_handler = self
+ @parser = LibXML::XML::SaxParser.new
+ @parser.callbacks = self
+ @stack = []
+ end
+
+ def parse(data)
+ @parser.string = data.to_xml
+ @parser.parse
+ end
+
+ def on_start_element(element, attributes = {})
+ case element
+ when 'stream:stream'
+ raise "you crossed the streams!" unless Stream.new(attributes).valid_for?(@jid)
+ else
+ child, parent = LibXML::XML::NodeMap[element].new(attributes), @stack.last
+
+ parent << child unless parent.nil?
+ @stack << child
+ end
+ end
+
+ def on_end_element(element)
+ last = @stack.pop
+
+ raise "element mismatch: #{last.name} != #{element}" if last.name != element
+
+ Emp::Dispatcher.handle(last, @jid) if @stack.empty?
+ end
+
+ def on_characters(text)
+ @stack.last.content = text
+ end
+
+ #def method_missing(method_name, *attributes, &block)
+ # p [method_name, attributes, block]
+ #end
+ end
+end
26 emp-core/lib/emp-core/libxml_ext/node.rb
@@ -0,0 +1,26 @@
+module LibXML::XML
+ class Node
+ class << self
+ alias super_new new
+ end
+
+ def self.register(element)
+ NodeMap[element] = self
+ end
+
+ def self.new(*args)
+ args, opts = args_and_opts(*args)
+ node = if !args.empty? || self == LibXML::XML::Node
+ super_new(*(args << opts))
+ else
+ super_new(NodeMap.lookup(self))
+ end
+
+ self.default_attributes.merge(opts).each do |k,v|
+ node[k] = v
+ end
+
+ node
+ end
+ end
+end
19 emp-core/lib/emp-core/libxml_ext/node_map.rb
@@ -0,0 +1,19 @@
+module LibXML::XML::NodeMap
+ class << self
+ attr_accessor :map
+ end
+
+ self.map = {}
+
+ def self.[]=(element, klass)
+ self.map[element] = klass
+ end
+
+ def self.[](element)
+ self.map[element] || LibXML::XML::Node
+ end
+
+ def self.lookup(klass)
+ self.map.invert[klass]
+ end
+end
20 emp-core/lib/emp-core/nodes/auth.rb
@@ -0,0 +1,20 @@
+module Emp
+ class Auth < Node
+ register 'auth'
+
+ self.default_attributes = {
+ 'xmlns' => 'urn:ietf:params:xml:ns:xmpp-sasl'
+ }
+
+ def self.plain(jid)
+ content = Base64::encode64("#{jid.node}@#{jid.host}\x00#{jid.node}\x00#{jid.passwd}").gsub(/\s/, '')
+ plain = new('mechanism' => 'PLAIN')
+ plain.content = content
+ plain
+ end
+
+ def self.digest_md5
+ new('mechanism' => 'DIGEST-MD5')
+ end
+ end
+end
22 emp-core/lib/emp-core/nodes/bind.rb
@@ -0,0 +1,22 @@
+module Emp
+ class Bind < Node
+ register "bind"
+
+ self.default_attributes = {
+ 'xmlns' => 'urn:ietf:params:xml:ns:xmpp-bind'
+ }
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ iq = InfoQuery.set('id' => 'bind_1')
+ iq << bind = Bind.new
+
+ unless jid.resource.empty?
+ bind << resource = Resource.new
+ resource.content = jid.resource
+ end
+
+ jid.xmpp_handler.send_data(Presence.initial)
+ end
+ end
+end
5 emp-core/lib/emp-core/nodes/body.rb
@@ -0,0 +1,5 @@
+module Emp
+ class Body < Node
+ register "body"
+ end
+end
10 emp-core/lib/emp-core/nodes/challenge.rb
@@ -0,0 +1,10 @@
+module Emp
+ class Challenge < Node
+ register "challenge"
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ #...
+ end
+ end
+end
8 emp-core/lib/emp-core/nodes/doctype.rb
@@ -0,0 +1,8 @@
+module Emp
+ class Doctype < Emp::Node
+
+ def self.to_xml
+ "<?xml version='1.0'?>"
+ end
+ end
+end
6 emp-core/lib/emp-core/nodes/jid_node.rb
@@ -0,0 +1,6 @@
+module Emp
+ # having JidNode & Jid is dumb
+ class JidNode < Node
+ register "jid"
+ end
+end
12 emp-core/lib/emp-core/nodes/mechanism.rb
@@ -0,0 +1,12 @@
+module Emp
+ class Mechanism < Node
+ register "mechanism"
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ if content == "PLAIN"
+ jid.xmpp_handler.send_data(Auth.plain(jid))
+ end
+ end
+ end
+end
10 emp-core/lib/emp-core/nodes/mechanisms.rb
@@ -0,0 +1,10 @@
+module Emp
+ class Mechanisms < Node
+ register "mechanisms"
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ children.each {|c| c.handle(jid)}
+ end
+ end
+end
13 emp-core/lib/emp-core/nodes/node.rb
@@ -0,0 +1,13 @@
+module Emp
+ class Node < LibXML::XML::Node
+ def self.inherited(klass)
+ class << klass
+ attr_accessor :default_attributes
+ end
+
+ klass.default_attributes = {}
+ end
+
+ alias_method :to_xml, :to_s
+ end
+end
10 emp-core/lib/emp-core/nodes/register.rb
@@ -0,0 +1,10 @@
+module Emp
+ class Register < Node
+ register "register"
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ #...
+ end
+ end
+end
5 emp-core/lib/emp-core/nodes/resource.rb
@@ -0,0 +1,5 @@
+module Emp
+ class Resource < Node
+ register "resource"
+ end
+end
9 emp-core/lib/emp-core/nodes/session.rb
@@ -0,0 +1,9 @@
+module Emp
+ class Session < Node
+ register "session"
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ end
+ end
+end
23 emp-core/lib/emp-core/nodes/stream.rb
@@ -0,0 +1,23 @@
+module Emp
+ class Stream < Node
+ register 'stream:stream'
+
+ self.default_attributes = {
+ 'xmlns' => 'jabber:client',
+ 'xmlns:stream' => 'http://etherx.jabber.org/streams',
+ 'version' => '1.0'
+ }
+
+ def self.to(host)
+ new('to' => host)
+ end
+
+ def valid_for?(jid)
+ jid.host == self['from']
+ end
+
+ def to_xml
+ to_s.gsub(/\/>$/, '>')
+ end
+ end
+end
10 emp-core/lib/emp-core/nodes/stream_features.rb
@@ -0,0 +1,10 @@
+module Emp
+ class StreamFeatures < Node
+ register 'stream:features'
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ children.each {|c| c.handle(jid)}
+ end
+ end
+end
10 emp-core/lib/emp-core/nodes/success.rb
@@ -0,0 +1,10 @@
+module Emp
+ class Success < Node
+ register "success"
+
+ ## HACK: Node's shouldn't have a handle method
+ def handle(jid)
+ jid.xmpp_handler.send_data(Stream.to(jid.host))
+ end
+ end
+end
10 emp-core/lib/emp-core/post_init.rb
@@ -0,0 +1,10 @@
+module Emp
+ class PostInit
+ cattr_accessor :callback_map
+ self.callback_map = Hash.new {|h, k| h[k] = []}
+
+ def self.add_callback(jid, &proc)
+ self.callback_map[jid] << proc
+ end
+ end
+end
13 emp-core/lib/emp-core/route/route.rb
@@ -0,0 +1,13 @@
+module Emp
+ class Route
+ attr_accessor :jid, :stanza, :attrs, :component, :action
+
+ def initialize(jid, stanza, attrs)
+ @jid, @stanza, @attrs = jid, stanza, attrs
+ end
+
+ def to(opts, component = opts[:component], action = opts[:action])
+ @component, @action = component, action
+ end
+ end
+end
32 emp-core/lib/emp-core/route/router.rb
@@ -0,0 +1,32 @@
+module Emp
+
+ # A stupid simple router
+ class Router
+ class << self
+ attr_accessor :jid_stack, :prep_procs
+ end
+
+ self.prep_procs = []
+
+ def self.prepare(&blk)
+ self.prep_procs << blk
+ end
+
+ def self.run
+ self.prep_procs.each do |proc|
+ self.jid_stack = [Jid.default]
+ instance_eval(&proc)
+ end
+ end
+
+ def self.resource(name, &blk)
+ self.jid_stack << Jid.find_or_create(Emp.node, Emp.host, name, Emp.passwd)
+ instance_eval(&blk)
+ self.jid_stack.pop
+ end
+
+ def self.route(stanza, attrs = {})
+ Dispatcher.add_route(Route.new(self.jid_stack.last, stanza, attrs))
+ end
+ end
+end
9 emp-core/lib/emp-core/stanzas/info_query.rb
@@ -0,0 +1,9 @@
+module Emp
+ class InfoQuery < Stanza
+ register 'iq'
+
+ def self.set(attributes = {})
+ new(attributes.merge('type' => 'set'))
+ end
+ end
+end
5 emp-core/lib/emp-core/stanzas/message.rb
@@ -0,0 +1,5 @@
+module Emp
+ class Message < Stanza
+ register "message"
+ end
+end
14 emp-core/lib/emp-core/stanzas/presence.rb
@@ -0,0 +1,14 @@
+module Emp
+ class Presence < Stanza
+ register 'presence'
+
+ def self.available(jid)
+ new('type' => 'available', 'from' => jid.to_s)
+ end
+
+ # TODO: this ain't working, need to dig around in xmpp4r some more
+ def self.initial
+ new
+ end
+ end
+end
4 emp-core/lib/emp-core/stanzas/stanza.rb
@@ -0,0 +1,4 @@
+module Emp
+ class Stanza < Node
+ end
+end
38 emp-core/spec/emp-core/unit/event_machine/handler_spec.rb
@@ -0,0 +1,38 @@
+require File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")
+
+describe Emp::XmppHandler do
+ describe "#initialize" do
+ before(:each) do
+ @sig = mock(:sig)
+ end
+
+ it "should require a client" do
+ lambda { Emp::XmppHandler.new(@sig) }.should raise_error(ArgumentError)
+ end
+
+ it "should set the client's xmpp_handler" do
+ client = mock(:client)
+ client.should_receive(:xmpp_handler=)
+
+ Emp::XmppHandler.new(@sig, client)
+ end
+ end
+
+ describe "send_data" do
+ before(:each) do
+ @client, @sig = mock(:client), mock(:sig)
+ @client.stub!(:xmpp_handler=)
+
+ @handler = Emp::XmppHandler.new(@sig, @client)
+ end
+
+ it "should call #to_xml on the data" do
+ pending "move to integration specs"
+
+ data = mock(:data)
+ data.should_receive(:to_xml).and_return ""
+
+ @handler.send_data data
+ end
+ end
+end
26 emp-core/spec/emp-core/unit/libxml/handler_spec.rb
@@ -0,0 +1,26 @@
+require File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")
+
+describe Emp::XmppHandler do
+ describe "#initialize" do
+
+ it "should require a client" do
+ lambda { Emp::XmlHandler.new }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "#parse" do
+ before(:each) do
+ @client, @sig = mock(:client), mock(:sig)
+ @client.stub!(:xmpp_handler=)
+
+ @handler = Emp::XmlHandler.new(@client)
+ end
+
+ it "should call #to_xml on the data" do
+ data = mock(:data)
+ data.should_receive(:to_xml).and_return ""
+
+ @handler.parse data
+ end
+ end
+end
6 emp-core/spec/spec_helper.rb
@@ -0,0 +1,6 @@
+require "rubygems"
+require "spec"
+require File.join(File.dirname(__FILE__), "..", "lib", "empp-core")
+
+Spec::Runner.configure do |config|
+end
24 emp-examples/hack/hack.rb
@@ -0,0 +1,24 @@
+$:.unshift "#{File.dirname(__FILE__)}/../../emp-core/lib"
+
+require 'emp-core'
+
+class Hackity < Emp::EmpComponent
+ def hack(message)
+ reply "Hackity Hack!" # Message.new("Hackity Hack!")
+ end
+end
+
+
+Emp.setup('hackity@localhost', 5222, 'hack')
+
+Emp::Router.prepare do
+
+ # hackity@localhost/hack
+ resource('hack') do
+
+ # route(<stanza type>, <matching attributes>) #TODO: use XPath to route these
+ route(:message, :from => 'bar@localhost').to(:component => 'hackity', :action => 'hack')
+ end
+end
+
+Emp.start
94 emp-examples/tweeter/tweeter.rb
@@ -0,0 +1,94 @@
+$:.unshift "#{File.dirname(__FILE__)}/../../emp-core/lib"
+
+require 'emp-core'
+require 'dm-core'
+
+DataMapper.setup(:default, 'sqlite3::memory:')
+
+class User
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :username, String, :length => 256
+ property :passwd, String, :length => 256
+
+ has n, :tweets, :class_name => "Tweet"
+
+ def to_xml
+ to_vcard
+ end
+
+ def to_vcard
+ #...
+ end
+end
+
+class Tweet
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :message, String, :length => 160
+
+ belongs_to :user
+
+ def to_xml
+ to_atom
+ end
+
+ def to_atom
+ #...
+ end
+end
+
+class Tweets < Emp::EmpComponent
+ def index
+ result Tweet.all # InfoQuery.new(Tweet.all.map{|t| t.to_xml}, :type => :result)
+ end
+
+ def show(id)
+ result Tweet.get(id) # InfoQuery.new(Tweet.get(id).to_xml, :type => :result)
+ end
+
+ # i'm thinking the stanza parameter should always be the XMPP payload, specific parameters
+ # can be grabbed from the attributes then inner elements using the merb-action-args approach
+ def create(stanza)
+ #<iq type='set'><message ...>...</message>...<message ...></message></iq>
+ @tweets = stanza.children.map {|m| Tweet.from_xml(m)}
+
+ if @tweets.all? {|t| t.save }
+ result :success # TODO: this is hackish
+ else
+ # some sort of error response
+ end
+ end
+end
+
+class Users < Emp::EmpComponent
+ def index
+ result User.all
+ end
+
+ def show(id)
+ @user = User.get(id) || (raise UserNotFound, id) # raise .. will reply with InfoQuery.new(<some error xml to be determined later>, :type => :error)
+ end
+end
+
+Emp.setup('tweeter@localhost', 5222, 'p@ssw0rd')
+
+Emp::Router.prepare do
+
+ resource('tweets') do
+ # :id => /\d+/ assumes Regex <=> XPath conversion
+ route(:info_query, :type => :get, :id => /\d+/).to(:component => 'tweets', :action => 'show')
+ route(:info_query, :type => :get).to(:component => 'tweets', :action => 'index')
+ route(:info_query, :type => :set, :id => /\d+/).to(:component => 'tweets', :action => 'update')
+ route(:info_query, :type => :set).to(:component => 'tweets', :action => 'create')
+ end
+
+ resource('tweeters') do
+ route(:info_query, :type => :get, :id => /\d+/).to(:compenent => 'tweeters', :action => 'show')
+ route(:info_query, :type => :get).to(:component => 'tweets', :action => 'index')
+ end
+end
+
+Emp.start

0 comments on commit 3f76758

Please sign in to comment.
Something went wrong with that request. Please try again.