julien51-babylon 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +89 -0
- data/bin/babylon +12 -0
- data/lib/babylon.rb +24 -0
- data/lib/babylon/base/controller.rb +62 -0
- data/lib/babylon/base/view.rb +50 -0
- data/lib/babylon/component_connection.rb +61 -0
- data/lib/babylon/router.rb +79 -0
- data/lib/babylon/runner.rb +31 -0
- data/lib/babylon/xmpp_connection.rb +161 -0
- data/templates/babylon/app/controllers/README.rdoc +13 -0
- data/templates/babylon/app/models/README.rdoc +1 -0
- data/templates/babylon/app/views/README.rdoc +12 -0
- data/templates/babylon/config/boot.rb +8 -0
- data/templates/babylon/config/config.yaml +28 -0
- data/templates/babylon/config/routes.yaml +15 -0
- data/templates/babylon/scripts/component +2 -0
- data/templates/babylon/scripts/generate +47 -0
- metadata +95 -0
data/README.rdoc
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
= babylon
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
Babylon is a framework to EventMachine based XMPP External Components in Ruby.
|
6
|
+
|
7
|
+
== FEATURES/PROBLEMS:
|
8
|
+
|
9
|
+
* This hasn't been tested.
|
10
|
+
* Problems with quotes and double-quotes in text that are not handled correctly... I spent too many hours on this! Please help!
|
11
|
+
|
12
|
+
== ROADMAP :
|
13
|
+
|
14
|
+
- Instead of passing the stanzas as Nokogiri:XML:Node elements we should find a way to pass an "agnostic" datastructure, so that app developers don't have to learn/know/understand Nokogiri
|
15
|
+
- Adding the ability to send several messages all at once (like render :collection in Rails)
|
16
|
+
|
17
|
+
== SYNOPSIS:
|
18
|
+
|
19
|
+
1. Install the gem
|
20
|
+
2. The app contains a generator that will "build" a scaffold for your application.
|
21
|
+
|
22
|
+
$> babylon myapp
|
23
|
+
|
24
|
+
3. Write your own "controllers"
|
25
|
+
|
26
|
+
/app/controllers/message_controller.rb
|
27
|
+
|
28
|
+
class MessageController < Babylon::Base::Controller
|
29
|
+
# Each controller is initiated with the stanza.(similar to params in rails actions)
|
30
|
+
|
31
|
+
def echo
|
32
|
+
# We do stuff here
|
33
|
+
@to = @stanza.attributes["from"].text
|
34
|
+
@from = @stanza.attributes["from"].text
|
35
|
+
@response = @stanza.elements["//body"].text
|
36
|
+
# By default, this would "render" the echo.xml.builder
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
4. Add the corresponding views (used to generate the messages). Babylon uses the same file conventions as Rails : a subdirectory for each controller, and one file per action :
|
41
|
+
Compared to Rails, we are using accessors (and not @variables assigned in the controller).
|
42
|
+
|
43
|
+
/app/views/message/echo.xml.builder
|
44
|
+
|
45
|
+
self.message(:to => to, :from => from, :type => :chat) do
|
46
|
+
self.body(response)
|
47
|
+
end
|
48
|
+
|
49
|
+
5. And finally start the component :
|
50
|
+
|
51
|
+
script/component
|
52
|
+
|
53
|
+
== ADDITIONAL INFORMATION
|
54
|
+
|
55
|
+
This code hasn't been tested at all! It's just a proof of concept. Feel free to pull, branch, improve {code|specs|tests|docs} and we will merge it!
|
56
|
+
|
57
|
+
== REQUIREMENTS:
|
58
|
+
|
59
|
+
Our goal is to limit the number of dependencies. Nokogiri seems to be only XML Library on Ruby that has a Push SAX Parser, that is why we are using it. It also seems pretty 'fast'.
|
60
|
+
Gems : Eventmachine, nokogiri
|
61
|
+
|
62
|
+
== INSTALL:
|
63
|
+
|
64
|
+
- sudo gem install babylon
|
65
|
+
|
66
|
+
== LICENSE:
|
67
|
+
|
68
|
+
(The MIT License)
|
69
|
+
|
70
|
+
Copyright (c) 2009 Julien Genestoux http://notifixio.us
|
71
|
+
|
72
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
73
|
+
a copy of this software and associated documentation files (the
|
74
|
+
'Software'), to deal in the Software without restriction, including
|
75
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
76
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
77
|
+
permit persons to whom the Software is furnished to do so, subject to
|
78
|
+
the following conditions:
|
79
|
+
|
80
|
+
The above copyright notice and this permission notice shall be
|
81
|
+
included in all copies or substantial portions of the Software.
|
82
|
+
|
83
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
84
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
85
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
86
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
87
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
88
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
89
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/babylon
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Generates the stubs for a new Babylon Application
|
4
|
+
# This will generate the right hierarchy for a Babylon App
|
5
|
+
# First, let's create the app directoryn based in ARGV[0]
|
6
|
+
|
7
|
+
if ARGV[0]
|
8
|
+
puts "Creating app '#{ARGV[0]}' in #{Dir.pwd}..."
|
9
|
+
FileUtils.cp_r "#{File.dirname(__FILE__)}/../templates/babylon", "#{Dir.pwd}/#{ARGV[0]}"
|
10
|
+
else
|
11
|
+
puts "Syntax : $> babylon app_name "
|
12
|
+
end
|
data/lib/babylon.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
require 'babylon/xmpp_connection'
|
8
|
+
require 'babylon/component_connection'
|
9
|
+
require 'babylon/router'
|
10
|
+
require 'babylon/runner'
|
11
|
+
require 'babylon/base/controller'
|
12
|
+
require 'babylon/base/view'
|
13
|
+
|
14
|
+
# Babylon is a XMPP Component Framework based on EventMachine. It uses the Nokogiri GEM, which is a Ruby wrapper for Libxml2.
|
15
|
+
# It implements the MVC paradigm.
|
16
|
+
# You can create your own application by running :
|
17
|
+
# $> babylon app_name
|
18
|
+
# This will generate some folders and files for your application. Please see README for further instructions
|
19
|
+
|
20
|
+
module Babylon
|
21
|
+
# 0.0.3 : Not suited for production, use at your own risks
|
22
|
+
VERSION = '0.0.3'
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Babylon
|
2
|
+
module Base
|
3
|
+
|
4
|
+
# Your application's controller should be descendant of this class.
|
5
|
+
|
6
|
+
class Controller
|
7
|
+
|
8
|
+
attr_accessor :stanza # Stanza received by the controller (Nokogiri::XML::Node)
|
9
|
+
|
10
|
+
# Creates a new controller (you should not override this class) and assigns the stanza
|
11
|
+
def initialize(params = {})
|
12
|
+
@stanza = params[:stanza]
|
13
|
+
@rendered = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Performs the action and calls back the optional block argument : you should not override this function
|
17
|
+
def perform(action, &block)
|
18
|
+
@action_name = action
|
19
|
+
@block = block
|
20
|
+
self.send(@action_name)
|
21
|
+
self.render
|
22
|
+
end
|
23
|
+
|
24
|
+
# Called by default after each action to "build" a XMPP stanza. By default, it will use the /controller_name/action.xml.builder
|
25
|
+
def render(options = nil)
|
26
|
+
return if @rendered # Avoid double rendering
|
27
|
+
|
28
|
+
if options.nil? # default rendering
|
29
|
+
return render(:file => default_template_name)
|
30
|
+
elsif action_name = options[:action]
|
31
|
+
return render(:file => default_template_name(action_name.to_s))
|
32
|
+
end
|
33
|
+
render_for_file(options[:file])
|
34
|
+
|
35
|
+
# And finally, we set up rendered to be true
|
36
|
+
@rendered = true
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# Used to transfer the assigned variables from the controller to the views
|
42
|
+
def hashed_variables
|
43
|
+
vars = Hash.new
|
44
|
+
instance_variables.each do |var|
|
45
|
+
vars[var[1..-1]] = instance_variable_get(var)
|
46
|
+
end
|
47
|
+
return vars
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default template name used to build stanzas
|
51
|
+
def default_template_name(action_name = nil)
|
52
|
+
"app/views/#{self.class.name.gsub("Controller","").downcase}/#{action_name || @action_name}.xml.builder"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates the view and "evaluates" it to build the XML for the stanza
|
56
|
+
def render_for_file(file)
|
57
|
+
view = Babylon::Base::View.new(file, hashed_variables)
|
58
|
+
@block.call(view.evaluate)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Babylon
|
2
|
+
module Base
|
3
|
+
|
4
|
+
##
|
5
|
+
# Your application's views (stanzas) should be descendant of this class.
|
6
|
+
class View
|
7
|
+
attr_reader :output
|
8
|
+
|
9
|
+
##
|
10
|
+
# Instantiate a new view with the various varibales passed in assigns and the path of the template to render.
|
11
|
+
def initialize(path, assigns)
|
12
|
+
@assigns = assigns
|
13
|
+
@output = ""
|
14
|
+
@view_template = path
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# "Loads" the view file, and uses the Nokogiri Builder to build the XML stanzas that will be sent.
|
19
|
+
def evaluate
|
20
|
+
evaluate_assigns
|
21
|
+
view_content = File.read(@view_template)
|
22
|
+
xml = Nokogiri::XML::Builder.new do
|
23
|
+
instance_eval(view_content)
|
24
|
+
end
|
25
|
+
return xml.doc.root.to_xml #we return the doc's root (to avoid the instruct)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Evaluate the local assigns and pushes them to the view.
|
30
|
+
def evaluate_assigns
|
31
|
+
unless @assigns_added
|
32
|
+
assign_variables_from_controller
|
33
|
+
@assigns_added = true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Assigns instance variables from the controller to the view.
|
39
|
+
def assign_variables_from_controller
|
40
|
+
@assigns.each do |key, value|
|
41
|
+
instance_variable_set("@#{key}", value)
|
42
|
+
self.class.send(:define_method, key) do # Defining accessors
|
43
|
+
value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Babylon
|
2
|
+
##
|
3
|
+
# ComponentConnection is in charge of the XMPP connection itself.
|
4
|
+
# Upon stanza reception, and depending on the status (connected... etc), this component will handle or forward the stanzas.
|
5
|
+
class ComponentConnection < XmppConnection
|
6
|
+
require 'digest/sha1'
|
7
|
+
|
8
|
+
##
|
9
|
+
# Creates a new ComponentConnection and waits for data in the stream
|
10
|
+
def initialize(*a)
|
11
|
+
super
|
12
|
+
@state = :wait_for_stream
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# XMPP Component handshake as defined in XEP-0114:
|
17
|
+
# http://xmpp.org/extensions/xep-0114.html
|
18
|
+
def receive_stanza(stanza)
|
19
|
+
case @state
|
20
|
+
|
21
|
+
when :wait_for_stream
|
22
|
+
if stanza.name == "stream:stream" && stanza.attributes['id']
|
23
|
+
# This means the XMPP session started!
|
24
|
+
# We must send the handshake now.
|
25
|
+
hash = Digest::SHA1::hexdigest(stanza.attributes['id'].content + @config['password'])
|
26
|
+
handshake = Nokogiri::XML::Node.new("handshake", stanza.document)
|
27
|
+
handshake.content = hash
|
28
|
+
send(handshake)
|
29
|
+
@state = :wait_for_handshake
|
30
|
+
else
|
31
|
+
raise
|
32
|
+
end
|
33
|
+
|
34
|
+
when :wait_for_handshake
|
35
|
+
if stanza.name == "handshake"
|
36
|
+
# Awesome, we're now connected and authentified, let's
|
37
|
+
# callback the controllers to tell them we're connected!
|
38
|
+
# TODO
|
39
|
+
@state = :connected
|
40
|
+
else
|
41
|
+
raise
|
42
|
+
end
|
43
|
+
|
44
|
+
when :connected
|
45
|
+
super # Can be dispatched
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Namespace of the component
|
51
|
+
def stream_namespace
|
52
|
+
'jabber:component:accept'
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Jid of the component
|
57
|
+
def stream_to
|
58
|
+
@config['jid']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Babylon
|
2
|
+
|
3
|
+
##
|
4
|
+
# The router is in charge of sending the right stanzas to the right controllers based on user defined Routes.
|
5
|
+
module Router
|
6
|
+
|
7
|
+
##
|
8
|
+
# Add several routes to the router
|
9
|
+
# Routes should be of form {name => params}
|
10
|
+
def add_routes(routes)
|
11
|
+
routes.each do |name, params|
|
12
|
+
add_route(Route.new(name, params))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Insert a route and makes sure that the routes are sorted
|
18
|
+
def add_route(route)
|
19
|
+
@routes ||= []
|
20
|
+
@routes << route
|
21
|
+
@routes.sort! { |r1,r2|
|
22
|
+
r2.priority <=> r1.priority
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# Look for the first martching route and calls the correspondong action for the corresponding controller.
|
27
|
+
# Sends the response on the XMPP stream/
|
28
|
+
def route(connection, stanza)
|
29
|
+
@routes ||= []
|
30
|
+
@routes.each { |route|
|
31
|
+
if route.accepts?(connection, stanza)
|
32
|
+
# Here should happen the magic : call the controller
|
33
|
+
controller = route.controller.new({:stanza => stanza})
|
34
|
+
controller.perform(route.action) do |response|
|
35
|
+
connection.send(response)
|
36
|
+
end
|
37
|
+
return true
|
38
|
+
end
|
39
|
+
}
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
# Throw away all added routes from this router. Helpful for
|
44
|
+
# testing.
|
45
|
+
def purge_routes!
|
46
|
+
@routes = []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Main router where all dispatchers shall register.
|
52
|
+
module CentralRouter
|
53
|
+
extend Router
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Route class which associate an XPATH match and a priority to a controller and an action
|
58
|
+
class Route
|
59
|
+
|
60
|
+
attr_reader :priority, :controller, :action
|
61
|
+
|
62
|
+
##
|
63
|
+
# Creates a new route
|
64
|
+
def initialize(name, params)
|
65
|
+
@priority = params["priority"]
|
66
|
+
@xpath = params["xpath"]
|
67
|
+
@controller = Kernel.const_get("#{params["controller"].capitalize}Controller")
|
68
|
+
@action = params["action"]
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Checks that the route matches the stanzas and calls the the action on the controller
|
73
|
+
def accepts?(connection, stanza)
|
74
|
+
stanza.xpath(@xpath, stanza.namespaces).first ? self : false
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Babylon
|
2
|
+
|
3
|
+
##
|
4
|
+
# Runner is in charge of running the application.
|
5
|
+
class Runner
|
6
|
+
|
7
|
+
##
|
8
|
+
# When run is called, it loads the configuration, the routes and add them into the router
|
9
|
+
# It then loads the models.
|
10
|
+
# Finally it starts the EventMachine and connect the ComponentConnection
|
11
|
+
def self.run(env = "development")
|
12
|
+
config = YAML::load(File.new('config/config.yaml'))[env]
|
13
|
+
routes = YAML::load(File.new('config/routes.yaml')) || []
|
14
|
+
|
15
|
+
# Adding Routes
|
16
|
+
CentralRouter.add_routes(routes)
|
17
|
+
|
18
|
+
# Requiring all models
|
19
|
+
Dir.glob('app/models/*.rb').each do |f|
|
20
|
+
require f
|
21
|
+
end
|
22
|
+
|
23
|
+
# Starting the EventMachine
|
24
|
+
EventMachine.epoll
|
25
|
+
EventMachine::run do
|
26
|
+
Babylon::ComponentConnection.connect(config)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Babylon
|
2
|
+
|
3
|
+
##
|
4
|
+
# Connection Exception
|
5
|
+
class NotConnected < Exception; end
|
6
|
+
|
7
|
+
##
|
8
|
+
# This class is in charge of handling the network connection to the XMPP server.
|
9
|
+
class XmppConnection < EventMachine::Connection
|
10
|
+
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
##
|
14
|
+
# Connects the XmppConnection to the right host with the right port.
|
15
|
+
# It passes itself (as handler) and the configuration
|
16
|
+
def self.connect(config)
|
17
|
+
EventMachine::connect config['host'], config['port'], self, config
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Called when the connection is terminated and stops the event loop
|
22
|
+
def unbind()
|
23
|
+
EventMachine::stop_event_loop
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Instantiate the Handler (called internally by EventMachine) and attaches a new XmppParser
|
28
|
+
def initialize(config)
|
29
|
+
@config = config
|
30
|
+
super()
|
31
|
+
@parser = XmppParser.new(&method(:receive_stanza))
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Called when a full stanza has been received and returns it to the central router to be sent to the corresponding controller. Eventually it displays this data for debugging purposes
|
36
|
+
def receive_stanza(stanza)
|
37
|
+
puts "<< #{stanza}\n" if debug? # Low level Logging
|
38
|
+
# If not handled by subclass (for authentication)
|
39
|
+
CentralRouter.route self, stanza
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream to establish the XMPP connection itself
|
44
|
+
# We use a "tweak" here to send only the starting tag of stream:stream
|
45
|
+
def connection_completed
|
46
|
+
super
|
47
|
+
builder = Nokogiri::XML::Builder.new {
|
48
|
+
self.send('stream:stream', 'xmlns' => "jabber:component:accept", 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['jid']) {
|
49
|
+
paste_content_here # The stream:stream element should be cut here ;)
|
50
|
+
}
|
51
|
+
}
|
52
|
+
@start_stream, @stop_stream = builder.to_xml.split('<paste_content_here/>')
|
53
|
+
send_data(@start_stream)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Sends data (string) on the stream. Eventually it displays this data for debugging purposes
|
58
|
+
def send(xml)
|
59
|
+
puts ">> #{xml}\n" if debug? # Very low level Logging
|
60
|
+
send_data xml.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
##
|
66
|
+
# receive_data is called when data is received. It is then passed to the parser.
|
67
|
+
def receive_data(data)
|
68
|
+
@parser.parse data
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Pretty self-explanatory ;)
|
73
|
+
def debug?
|
74
|
+
@config["debug"]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# This is the XML SAX Parser that accepts "pushed" content
|
80
|
+
class XmppParser < Nokogiri::XML::SAX::Document
|
81
|
+
|
82
|
+
##
|
83
|
+
# Initialize the parser and adds the callback that will be called upen stanza completion
|
84
|
+
def initialize(&callback)
|
85
|
+
@callback = callback
|
86
|
+
super()
|
87
|
+
@parser = Nokogiri::XML::SAX::Parser.new(self)
|
88
|
+
@doc = nil
|
89
|
+
@elem = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Parses the received data
|
94
|
+
def parse(data)
|
95
|
+
@parser.parse data
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Called when the document received in the stream is started
|
100
|
+
def start_document
|
101
|
+
@doc = Nokogiri::XML::Document.new
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Adds characters to the current element (being parsed)
|
106
|
+
def characters(string)
|
107
|
+
@elem.add_child(Nokogiri::XML::Text.new(string, @doc))
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Instantiate a new current Element, adds the corresponding attributes and namespaces
|
112
|
+
# The new element is eventually added to a parent element (if present).
|
113
|
+
# If this element is the first element (the root of the document), then instead of adding it to a parent, we add it to the document itself. In this case, the current element will not be terminated, so we activate the callback immediately.
|
114
|
+
def start_element(qname, attributes = [])
|
115
|
+
e = Nokogiri::XML::Element.new(qname, @doc)
|
116
|
+
add_namespaces_and_attributes_to_node(attributes, e)
|
117
|
+
|
118
|
+
# If we don't have any elem yet, we are at the root
|
119
|
+
@elem = @elem ? @elem.add_child(e) : (@root = e)
|
120
|
+
|
121
|
+
if @elem.parent.nil?
|
122
|
+
# Should be called only for stream:stream
|
123
|
+
@doc.root = @elem
|
124
|
+
@callback.call(@elem)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Terminates the current element and calls the callback
|
130
|
+
def end_element(name)
|
131
|
+
if @elem
|
132
|
+
if @elem.parent == @root
|
133
|
+
@callback.call(@elem)
|
134
|
+
# And we also need to remove @elem from its tree
|
135
|
+
@elem.unlink
|
136
|
+
# And the current elem is the root
|
137
|
+
@elem = @root
|
138
|
+
else
|
139
|
+
@elem = @elem.parent
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
##
|
147
|
+
# Adds namespaces and attributes. Nokogiri passes them as a array of [name, value, name, value]...
|
148
|
+
def add_namespaces_and_attributes_to_node(attrs, node)
|
149
|
+
(attrs.size / 2).times do |i|
|
150
|
+
name, value = attrs[2 * i], attrs[2 * i + 1]
|
151
|
+
if name =~ /xmlns/
|
152
|
+
node.add_namespace(name, value)
|
153
|
+
else
|
154
|
+
node.set_attribute name, value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
You can define you class models here. It is totally ok to reuse your ActiveRecord from another application!
|
@@ -0,0 +1,12 @@
|
|
1
|
+
= Babylon::Base::View
|
2
|
+
|
3
|
+
== Usage
|
4
|
+
|
5
|
+
Please see Babylon Rdoc. Put all the views related to controller MyController into app/views/my/...
|
6
|
+
This file are Xml Builder Files (see Nokogiri Documentation for any doubt).
|
7
|
+
|
8
|
+
== Example
|
9
|
+
|
10
|
+
self.message(:to => to, :from => from, :type => :chat) do
|
11
|
+
self.body(body) // Same as self.send(:body, body) (
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# This contains the global configuration of your component.
|
2
|
+
# env:
|
3
|
+
# jid: your.component.jid
|
4
|
+
# password: your.component.password
|
5
|
+
# host: host on which the XMPP server is running
|
6
|
+
# port: port to which your component should connect
|
7
|
+
# debug: outputs the xmpp stanzas
|
8
|
+
|
9
|
+
development:
|
10
|
+
jid: component.server.com
|
11
|
+
password: password
|
12
|
+
host: localhost
|
13
|
+
port: 5278
|
14
|
+
debug: true
|
15
|
+
|
16
|
+
test:
|
17
|
+
jid: component.server.com
|
18
|
+
password: password
|
19
|
+
host: localhost
|
20
|
+
port: 5278
|
21
|
+
debug: false
|
22
|
+
|
23
|
+
production:
|
24
|
+
jid: component.server.com
|
25
|
+
password: password
|
26
|
+
host: localhost
|
27
|
+
port: 5278
|
28
|
+
debug: false
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This determines the routing for your controllers
|
2
|
+
# The macthing is based on XPATH
|
3
|
+
|
4
|
+
# route_name:
|
5
|
+
# priority: if a stanza matches several XPath conditions, the one with the greater priority will be executed. If they have the same priority, one will be chose randomly, so choose your priority very carefully! It is a good practice to attribute a different priority to each of your routes, so that there is no conflict
|
6
|
+
# controller: name of the controller that should handle the stanza
|
7
|
+
# action: action to be called to handle the stanza
|
8
|
+
# xpath: used to macth the stanza
|
9
|
+
|
10
|
+
# my_route_1:
|
11
|
+
# priority: 1
|
12
|
+
# controller: main
|
13
|
+
# xpath: "//message"
|
14
|
+
# action: echo
|
15
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
##
|
4
|
+
# This is a generator that will build scaffold controller and add the corresponding routes
|
5
|
+
#
|
6
|
+
# == Usage
|
7
|
+
#
|
8
|
+
# $> scripts/generate controller_name [action_name:priority;Xpath, ...]
|
9
|
+
#
|
10
|
+
# == Example
|
11
|
+
#
|
12
|
+
# $> scripts/generate message echo:10:"//message/body" reverse:0:"//message"
|
13
|
+
#
|
14
|
+
# This will generate a an echo controller with 2 actions : echo and reverse. Echo will be called for stanza of type message witch a body than contains "echo", while reverse will be called for any other stanza of type message.
|
15
|
+
|
16
|
+
|
17
|
+
# Create the views subdirectory
|
18
|
+
Dir.mkdir "app/views/#{ARGV[0]}" if !File.exists?("app/views/#{ARGV[0]}")
|
19
|
+
# CReate the controller file and write the first lines
|
20
|
+
controller_file = File.open("app/controllers/#{ARGV[0]}_controller.rb", "w+")
|
21
|
+
route_file = File.open("config/routes.yaml", "a")
|
22
|
+
controller_file.puts "class #{ARGV[0].capitalize}Controller < Babylon::Base::Controller"
|
23
|
+
controller_file.puts ""
|
24
|
+
ARGV[1..-1].each do |action|
|
25
|
+
action_name, priority, xpath = action.split(":")
|
26
|
+
# Add the action to the controller
|
27
|
+
controller_file.puts ""
|
28
|
+
controller_file.puts " def #{action_name}"
|
29
|
+
controller_file.puts " # Called when the stanza matches #{xpath}. Priority #{priority}"
|
30
|
+
controller_file.puts " "
|
31
|
+
controller_file.puts " end"
|
32
|
+
# Create the file views
|
33
|
+
view_file = File.open("app/views/#{ARGV[0]}/#{action_name}.xml.builder", "w+")
|
34
|
+
view_file.puts "self.message(:to => to, :from => from, :type => :chat) do"
|
35
|
+
view_file.puts " self.body(content) # Same as self.send(:body, body) "
|
36
|
+
view_file.puts "end"
|
37
|
+
view_file.close
|
38
|
+
# And now add the route (we must be careful and not delete other routes)
|
39
|
+
route_file.puts ""
|
40
|
+
route_file.puts "#{ARGV[0]}_#{action_name}_route:"
|
41
|
+
route_file.puts " priority: #{priority}"
|
42
|
+
route_file.puts " controller: #{ARGV[0]}"
|
43
|
+
route_file.puts " xpath: #{xpath}"
|
44
|
+
route_file.puts " action: #{action_name}"
|
45
|
+
end
|
46
|
+
controller_file.puts "end"
|
47
|
+
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: julien51-babylon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julien Genestoux
|
8
|
+
- Astro
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-02-13 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: nokogiri
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: eventmachine
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
version:
|
36
|
+
description: Babylon is a framework to create EventMachine based XMPP External Components in Ruby.
|
37
|
+
email: babylon@notifixio.us
|
38
|
+
executables:
|
39
|
+
- babylon
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- README.rdoc
|
46
|
+
- lib/babylon.rb
|
47
|
+
- lib/babylon/base/controller.rb
|
48
|
+
- lib/babylon/base/view.rb
|
49
|
+
- lib/babylon/component_connection.rb
|
50
|
+
- lib/babylon/router.rb
|
51
|
+
- lib/babylon/runner.rb
|
52
|
+
- lib/babylon/xmpp_connection.rb
|
53
|
+
- bin/babylon
|
54
|
+
- spec
|
55
|
+
- templates
|
56
|
+
- templates/babylon
|
57
|
+
- templates/babylon/app
|
58
|
+
- templates/babylon/app/controllers/README.rdoc
|
59
|
+
- templates/babylon/app/models/README.rdoc
|
60
|
+
- templates/babylon/app/views/README.rdoc
|
61
|
+
- templates/babylon/config
|
62
|
+
- templates/babylon/config/routes.yaml
|
63
|
+
- templates/babylon/config/config.yaml
|
64
|
+
- templates/babylon/config/boot.rb
|
65
|
+
- templates/babylon/scripts
|
66
|
+
- templates/babylon/scripts/component
|
67
|
+
- templates/babylon/scripts/generate
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/julien51/babylon/
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.2.0
|
91
|
+
signing_key:
|
92
|
+
specification_version: 2
|
93
|
+
summary: Babylon is a framework to create EventMachine based XMPP External Components in Ruby.
|
94
|
+
test_files: []
|
95
|
+
|