mohiam-babylon 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +111 -0
- data/Rakefile +145 -0
- data/bin/babylon +6 -0
- data/lib/babylon.rb +119 -0
- data/lib/babylon/base/controller.rb +116 -0
- data/lib/babylon/base/stanza.rb +23 -0
- data/lib/babylon/base/view.rb +49 -0
- data/lib/babylon/client_connection.rb +210 -0
- data/lib/babylon/component_connection.rb +87 -0
- data/lib/babylon/generator.rb +139 -0
- data/lib/babylon/router.rb +103 -0
- data/lib/babylon/router/dsl.rb +61 -0
- data/lib/babylon/runner.rb +148 -0
- data/lib/babylon/xmpp_connection.rb +172 -0
- data/lib/babylon/xmpp_parser.rb +111 -0
- data/lib/babylon/xpath_helper.rb +13 -0
- data/spec/bin/babylon_spec.rb +0 -0
- data/spec/em_mock.rb +42 -0
- data/spec/lib/babylon/base/controller_spec.rb +205 -0
- data/spec/lib/babylon/base/stanza_spec.rb +15 -0
- data/spec/lib/babylon/base/view_spec.rb +86 -0
- data/spec/lib/babylon/client_connection_spec.rb +304 -0
- data/spec/lib/babylon/component_connection_spec.rb +135 -0
- data/spec/lib/babylon/generator_spec.rb +10 -0
- data/spec/lib/babylon/router/dsl_spec.rb +72 -0
- data/spec/lib/babylon/router_spec.rb +189 -0
- data/spec/lib/babylon/runner_spec.rb +213 -0
- data/spec/lib/babylon/xmpp_connection_spec.rb +197 -0
- data/spec/lib/babylon/xmpp_parser_spec.rb +275 -0
- data/spec/lib/babylon/xpath_helper_spec.rb +25 -0
- data/spec/spec_helper.rb +34 -0
- data/templates/babylon/app/controllers/controller.rb +7 -0
- data/templates/babylon/app/stanzas/stanza.rb +6 -0
- data/templates/babylon/app/views/view.rb +6 -0
- data/templates/babylon/config/boot.rb +16 -0
- data/templates/babylon/config/config.yaml +24 -0
- data/templates/babylon/config/dependencies.rb +1 -0
- data/templates/babylon/config/routes.rb +22 -0
- data/templates/babylon/log/development.log +0 -0
- data/templates/babylon/log/production.log +0 -0
- data/templates/babylon/log/test.log +52 -0
- data/templates/babylon/script/component +46 -0
- data/templates/babylon/tmp/pids/README +2 -0
- data/test/babylon_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +179 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module Babylon
|
2
|
+
module Base
|
3
|
+
##
|
4
|
+
# Class used to Parse a Stanza on the XMPP stream.
|
5
|
+
# You should have a Stanza subsclass for each of your controller actions, as they allow you to define which stanzas and which information is passed to yoru controllers.
|
6
|
+
#
|
7
|
+
# You can define your own macthing pretty easily with the element and elements methods, as explained in the SaxMachine Documentation: http://github.com/pauldix/sax-machine/tree/master
|
8
|
+
# if your stanza is a message stanza, you can match the following for example:
|
9
|
+
# element :message, :value => :to, :as => :to
|
10
|
+
# element :message, :value => :from, :as => :from
|
11
|
+
# element :message, :value => :id, :as => :stanza_id
|
12
|
+
# element :message, :value => :type, :as => :stanza_type
|
13
|
+
# element :message, :value => :"xml:lang", :as => :lang
|
14
|
+
#
|
15
|
+
class Stanza
|
16
|
+
include SAXMachine
|
17
|
+
|
18
|
+
def initialize(xml = nil)
|
19
|
+
parse(xml.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Babylon
|
2
|
+
module Base
|
3
|
+
|
4
|
+
class ViewFileNotFound < Errno::ENOENT; end
|
5
|
+
|
6
|
+
##
|
7
|
+
# Your application's views (stanzas) should be descendant of this class.
|
8
|
+
class View
|
9
|
+
attr_reader :view_template
|
10
|
+
|
11
|
+
##
|
12
|
+
# Used to 'include' another view inside an existing view.
|
13
|
+
# The caller needs to pass the context in which the partial will be rendered
|
14
|
+
# Render must be called with :partial as well (other options will be supported later). The partial vale should be a relative path
|
15
|
+
# to another file view, from the calling view.
|
16
|
+
def render(xml, options = {})
|
17
|
+
# First, we need to identify the partial file path, based on the @view_template path.
|
18
|
+
partial_path = (@view_template.split("/")[0..-2] + options[:partial].split("/")).join("/").gsub(".xml.builder", "") + ".xml.builder"
|
19
|
+
raise ViewFileNotFound, "No such file #{partial_path}" unless Babylon.views[partial_path]
|
20
|
+
eval(Babylon.views[partial_path], binding, partial_path, 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Instantiate a new view with the various varibales passed in assigns and the path of the template to render.
|
25
|
+
def initialize(path = "", assigns = {})
|
26
|
+
@view_template = path
|
27
|
+
|
28
|
+
assigns.each do |key, value|
|
29
|
+
# Babylon.logger.debug("assigning @#{key}='#{value}'")
|
30
|
+
instance_variable_set(:"@#{key}", value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# "Loads" the view file, and uses the Nokogiri Builder to build the XML stanzas that will be sent.
|
36
|
+
def evaluate
|
37
|
+
return if @view_template == ""
|
38
|
+
|
39
|
+
# Babylon.logger.debug("eval with template #{@view_template} - looking thru #{Babylon.views}")
|
40
|
+
raise ViewFileNotFound, "No such file #{@view_template}" unless Babylon.views[@view_template]
|
41
|
+
builder = Nokogiri::XML::Builder.new
|
42
|
+
builder.stream do |xml|
|
43
|
+
eval(Babylon.views[@view_template], binding, @view_template, 1)
|
44
|
+
end
|
45
|
+
builder.doc.root.children # we output the document built
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module Babylon
|
2
|
+
|
3
|
+
##
|
4
|
+
# ClientConnection is in charge of the XMPP connection for a Regular XMPP Client.
|
5
|
+
# So far, SASL Plain authenticationonly is supported
|
6
|
+
# Upon stanza reception, and depending on the status (connected... etc), this component will handle or forward the stanzas.
|
7
|
+
class ClientConnection < XmppConnection
|
8
|
+
|
9
|
+
attr_reader :binding_iq_id, :session_iq_id
|
10
|
+
|
11
|
+
##
|
12
|
+
# Creates a new ClientConnection and waits for data in the stream
|
13
|
+
def initialize(params)
|
14
|
+
super(params)
|
15
|
+
@state = :wait_for_stream
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Connects the ClientConnection based on SRV records for the jid's domain, if no host or port has been specified.
|
20
|
+
# In any case, we give priority to the specified host and port.
|
21
|
+
def self.connect(params, handler = nil)
|
22
|
+
return super(params, handler) if params["host"] && params["port"]
|
23
|
+
|
24
|
+
begin
|
25
|
+
srv = []
|
26
|
+
Resolv::DNS.open { |dns|
|
27
|
+
# If ruby version is too old and SRV is unknown, this will raise a NameError
|
28
|
+
# which is caught below
|
29
|
+
host_from_jid = params["jid"].split("/").first.split("@").last
|
30
|
+
Babylon.logger.debug {
|
31
|
+
"RESOLVING: _xmpp-client._tcp.#{host_from_jid} (SRV)"
|
32
|
+
}
|
33
|
+
srv = dns.getresources("_xmpp-client._tcp.#{host_from_jid}", Resolv::DNS::Resource::IN::SRV)
|
34
|
+
}
|
35
|
+
# Sort SRV records: lowest priority first, highest weight first
|
36
|
+
srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
|
37
|
+
# And now, for each record, let's try to connect.
|
38
|
+
srv.each { |record|
|
39
|
+
begin
|
40
|
+
params["host"] = record.target.to_s
|
41
|
+
params["port"] = Integer(record.port)
|
42
|
+
super(params, handler)
|
43
|
+
# Success
|
44
|
+
break
|
45
|
+
rescue NotConnected
|
46
|
+
# Try next SRV record
|
47
|
+
end
|
48
|
+
}
|
49
|
+
rescue NameError
|
50
|
+
Babylon.logger.debug {
|
51
|
+
"Resolv::DNS does not support SRV records. Please upgrade to ruby-1.8.3 or later! \n#{$!} : #{$!.backtrace.join("\n")}"
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Builds the stream stanza for this client
|
58
|
+
def stream_stanza
|
59
|
+
doc = Nokogiri::XML::Document.new
|
60
|
+
stream = Nokogiri::XML::Node.new("stream:stream", doc)
|
61
|
+
doc.add_child(stream)
|
62
|
+
stream["xmlns"] = stream_namespace
|
63
|
+
stream["xmlns:stream"] = "http://etherx.jabber.org/streams"
|
64
|
+
stream["to"] = jid.split("/").first.split("@").last
|
65
|
+
stream["version"] = "1.0"
|
66
|
+
paste_content_here = Nokogiri::XML::Node.new("paste_content_here", doc)
|
67
|
+
stream.add_child(paste_content_here)
|
68
|
+
doc.to_xml.split('<paste_content_here/>').first
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream
|
73
|
+
# to establish the XMPP connection itself.
|
74
|
+
# We use a "tweak" here to send only the starting tag of stream:stream
|
75
|
+
def connection_completed
|
76
|
+
super
|
77
|
+
send_xml(stream_stanza)
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Called upon stanza reception
|
82
|
+
# Marked as connected when the client has been SASLed, authenticated, biund to a resource and when the session has been created
|
83
|
+
def receive_stanza(stanza)
|
84
|
+
case @state
|
85
|
+
when :connected
|
86
|
+
super # Can be dispatched
|
87
|
+
|
88
|
+
when :wait_for_stream_authenticated
|
89
|
+
if stanza.name == "stream:stream" && stanza.attributes['id']
|
90
|
+
@state = :wait_for_bind
|
91
|
+
end
|
92
|
+
|
93
|
+
when :wait_for_stream
|
94
|
+
if stanza.name == "stream:stream" && stanza.attributes['id']
|
95
|
+
@state = :wait_for_auth_mechanisms
|
96
|
+
end
|
97
|
+
|
98
|
+
when :wait_for_auth_mechanisms
|
99
|
+
if stanza.name == "stream:features"
|
100
|
+
if stanza.at("starttls") # we shall start tls
|
101
|
+
doc = Nokogiri::XML::Document.new
|
102
|
+
starttls = Nokogiri::XML::Node.new("starttls", doc)
|
103
|
+
doc.add_child(starttls)
|
104
|
+
starttls["xmlns"] = "urn:ietf:params:xml:ns:xmpp-tls"
|
105
|
+
send_xml(starttls.to_s)
|
106
|
+
@state = :wait_for_proceed
|
107
|
+
elsif stanza.at("mechanisms") # tls is ok
|
108
|
+
if stanza.at("mechanisms").children.map() { |m| m.text }.include? "PLAIN"
|
109
|
+
doc = Nokogiri::XML::Document.new
|
110
|
+
auth = Nokogiri::XML::Node.new("auth", doc)
|
111
|
+
doc.add_child(auth)
|
112
|
+
auth['mechanism'] = "PLAIN"
|
113
|
+
auth["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"
|
114
|
+
auth.content = Base64::encode64([jid, jid.split("@").first, @password].join("\000")).gsub(/\s/, '')
|
115
|
+
send_xml(auth.to_s)
|
116
|
+
@state = :wait_for_success
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
when :wait_for_success
|
122
|
+
if stanza.name == "success" # Yay! Success
|
123
|
+
@state = :wait_for_stream_authenticated
|
124
|
+
@parser.reset
|
125
|
+
send_xml(stream_stanza)
|
126
|
+
elsif stanza.name == "failure"
|
127
|
+
if stanza.at("bad-auth") || stanza.at("not-authorized")
|
128
|
+
raise AuthenticationError
|
129
|
+
else
|
130
|
+
end
|
131
|
+
else
|
132
|
+
# Hum Failure...
|
133
|
+
end
|
134
|
+
|
135
|
+
when :wait_for_bind
|
136
|
+
if stanza.name == "stream:features"
|
137
|
+
if stanza.at("bind")
|
138
|
+
doc = Nokogiri::XML::Document.new
|
139
|
+
# Let's build the binding_iq
|
140
|
+
@binding_iq_id = Integer(rand(10000000))
|
141
|
+
iq = Nokogiri::XML::Node.new("iq", doc)
|
142
|
+
doc.add_child(iq)
|
143
|
+
iq["type"] = "set"
|
144
|
+
iq["id"] = binding_iq_id.to_s
|
145
|
+
bind = Nokogiri::XML::Node.new("bind", doc)
|
146
|
+
bind["xmlns"] = "urn:ietf:params:xml:ns:xmpp-bind"
|
147
|
+
iq.add_child(bind)
|
148
|
+
resource = Nokogiri::XML::Node.new("resource", doc)
|
149
|
+
if jid.split("/").size == 2
|
150
|
+
resource.content = (@jid.split("/").last)
|
151
|
+
else
|
152
|
+
resource.content = "babylon_client_#{binding_iq_id}"
|
153
|
+
end
|
154
|
+
bind.add_child(resource)
|
155
|
+
send_xml(iq.to_s)
|
156
|
+
@state = :wait_for_confirmed_binding
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
when :wait_for_confirmed_binding
|
161
|
+
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == binding_iq_id
|
162
|
+
if stanza.at("jid")
|
163
|
+
@jid = stanza.at("jid").text
|
164
|
+
end
|
165
|
+
# And now, we must initiate the session
|
166
|
+
@session_iq_id = Integer(rand(10000))
|
167
|
+
doc = Nokogiri::XML::Document.new
|
168
|
+
iq = Nokogiri::XML::Node.new("iq", doc)
|
169
|
+
doc.add_child(iq)
|
170
|
+
iq["type"] = "set"
|
171
|
+
iq["id"] = session_iq_id.to_s
|
172
|
+
session = Nokogiri::XML::Node.new("session", doc)
|
173
|
+
session["xmlns"] = "urn:ietf:params:xml:ns:xmpp-session"
|
174
|
+
iq.add_child(session)
|
175
|
+
send_xml(iq.to_s)
|
176
|
+
@state = :wait_for_confirmed_session
|
177
|
+
end
|
178
|
+
|
179
|
+
when :wait_for_confirmed_session
|
180
|
+
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == session_iq_id
|
181
|
+
# And now, send a presence!
|
182
|
+
doc = Nokogiri::XML::Document.new
|
183
|
+
presence = Nokogiri::XML::Node.new("presence", doc)
|
184
|
+
send_xml(presence.to_s)
|
185
|
+
begin
|
186
|
+
@handler.on_connected(self) if @handler and @handler.respond_to?("on_connected")
|
187
|
+
rescue
|
188
|
+
Babylon.logger.error {
|
189
|
+
"on_connected failed : #{$!}\n#{$!.backtrace.join("\n")}"
|
190
|
+
}
|
191
|
+
end
|
192
|
+
@state = :connected
|
193
|
+
end
|
194
|
+
|
195
|
+
when :wait_for_proceed
|
196
|
+
start_tls() # starting TLS
|
197
|
+
@state = :wait_for_stream
|
198
|
+
@parser.reset
|
199
|
+
send_xml stream_stanza
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Namespace of the client
|
205
|
+
def stream_namespace
|
206
|
+
"jabber:client"
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,87 @@
|
|
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
|
+
|
7
|
+
##
|
8
|
+
# Creates a new ComponentConnection and waits for data in the stream
|
9
|
+
def initialize(params)
|
10
|
+
super(params)
|
11
|
+
@state = :wait_for_stream
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream
|
16
|
+
# to establish the XMPP connection itself.
|
17
|
+
# We use a "tweak" here to send only the starting tag of stream:stream
|
18
|
+
def connection_completed
|
19
|
+
super
|
20
|
+
doc = Nokogiri::XML::Document.new
|
21
|
+
stream = Nokogiri::XML::Node.new("stream:stream", doc)
|
22
|
+
stream["xmlns"] = stream_namespace
|
23
|
+
stream["xmlns:stream"] = "http://etherx.jabber.org/streams"
|
24
|
+
stream["to"] = jid
|
25
|
+
doc.add_child(stream)
|
26
|
+
paste_content_here= Nokogiri::XML::Node.new("paste_content_here", doc)
|
27
|
+
stream.add_child(paste_content_here)
|
28
|
+
start, stop = doc.to_xml.split('<paste_content_here/>')
|
29
|
+
send_xml(start)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# XMPP Component handshake as defined in XEP-0114:
|
34
|
+
# http://xmpp.org/extensions/xep-0114.html
|
35
|
+
def receive_stanza(stanza)
|
36
|
+
case @state
|
37
|
+
when :connected # Most frequent case
|
38
|
+
super(stanza) # Can be dispatched
|
39
|
+
|
40
|
+
when :wait_for_stream
|
41
|
+
if stanza.name == "stream:stream" && stanza.attributes['id']
|
42
|
+
# This means the XMPP session started!
|
43
|
+
# We must send the handshake now.
|
44
|
+
send_xml(handshake(stanza))
|
45
|
+
@state = :wait_for_handshake
|
46
|
+
else
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
|
50
|
+
when :wait_for_handshake
|
51
|
+
if stanza.name == "handshake"
|
52
|
+
begin
|
53
|
+
@handler.on_connected(self) if @handler and @handler.respond_to?("on_connected")
|
54
|
+
rescue
|
55
|
+
Babylon.logger.error {
|
56
|
+
"on_connected failed : #{$!}\n#{$!.backtrace.join("\n")}"
|
57
|
+
}
|
58
|
+
end
|
59
|
+
@state = :connected
|
60
|
+
elsif stanza.name == "stream:error"
|
61
|
+
raise AuthenticationError
|
62
|
+
else
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Namespace of the component
|
71
|
+
def stream_namespace
|
72
|
+
'jabber:component:accept'
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def handshake(stanza)
|
78
|
+
hash = Digest::SHA1::hexdigest(stanza.attributes['id'].content + @password)
|
79
|
+
doc = Nokogiri::XML::Document.new
|
80
|
+
handshake = Nokogiri::XML::Node.new("handshake", doc)
|
81
|
+
doc.add_child(handshake)
|
82
|
+
handshake.content = hash
|
83
|
+
handshake
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Babylon
|
2
|
+
module Generator
|
3
|
+
extend Templater::Manifold
|
4
|
+
|
5
|
+
desc <<-DESC
|
6
|
+
Babylon is a framework to generate XMPP Applications in Ruby."
|
7
|
+
DESC
|
8
|
+
|
9
|
+
##
|
10
|
+
# Generates a Babylon Application
|
11
|
+
class ApplicationGenerator < Templater::Generator
|
12
|
+
desc <<-DESC
|
13
|
+
Generates the file architecture for a Babylon Application. To run, you MUST provide an application name"
|
14
|
+
DESC
|
15
|
+
|
16
|
+
first_argument :application_name, :required => true, :desc => "Your application name."
|
17
|
+
|
18
|
+
def self.source_root
|
19
|
+
File.join(File.dirname(__FILE__), '../../templates/babylon')
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create all subsdirectories
|
23
|
+
empty_directory :app_directory do |d|
|
24
|
+
d.destination = "#{application_name}/app"
|
25
|
+
end
|
26
|
+
empty_directory :controllers_directory do |d|
|
27
|
+
d.destination = "#{application_name}/app/controllers"
|
28
|
+
end
|
29
|
+
empty_directory :views_directory do |d|
|
30
|
+
d.destination = "#{application_name}/app/views"
|
31
|
+
end
|
32
|
+
empty_directory :views_directory do |d|
|
33
|
+
d.destination = "#{application_name}/app/stanzas"
|
34
|
+
end
|
35
|
+
empty_directory :models_directory do |d|
|
36
|
+
d.destination = "#{application_name}/app/models"
|
37
|
+
end
|
38
|
+
empty_directory :initializers_directory do |d|
|
39
|
+
d.destination = "#{application_name}/config/initializers"
|
40
|
+
end
|
41
|
+
empty_directory :tmp_directory do |d|
|
42
|
+
d.destination = "#{application_name}/tmp"
|
43
|
+
end
|
44
|
+
empty_directory :log_directory do |d|
|
45
|
+
d.destination = "#{application_name}/log"
|
46
|
+
end
|
47
|
+
empty_directory :pid_directory do |d|
|
48
|
+
d.destination = "#{application_name}/tmp/pids"
|
49
|
+
end
|
50
|
+
|
51
|
+
# And now add the critical files
|
52
|
+
file :boot_file do |f|
|
53
|
+
f.source = "#{source_root}/config/boot.rb"
|
54
|
+
f.destination = "#{application_name}/config/boot.rb"
|
55
|
+
end
|
56
|
+
file :config_file do |f|
|
57
|
+
f.source = "#{source_root}/config/config.yaml"
|
58
|
+
f.destination = "#{application_name}/config/config.yaml"
|
59
|
+
end
|
60
|
+
file :dependencies_file do |f|
|
61
|
+
f.source = "#{source_root}/config/dependencies.rb"
|
62
|
+
f.destination = "#{application_name}/config/dependencies.rb"
|
63
|
+
end
|
64
|
+
file :dependencies_file do |f|
|
65
|
+
f.source = "#{source_root}/config/routes.rb"
|
66
|
+
f.destination = "#{application_name}/config/routes.rb"
|
67
|
+
end
|
68
|
+
template :component_file do |f|
|
69
|
+
f.source = "#{source_root}/script/component"
|
70
|
+
f.destination = "#{application_name}/script/component"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Generates a new controller, with the corresponding stanzas and routes.
|
77
|
+
class ControllerGenerator < Templater::Generator
|
78
|
+
desc <<-DESC
|
79
|
+
Generates a new controller for the current Application. It also adds the corresponding routes and actions, based on a Xpath and priority. \nSyntax: babylon controller <controller_name> [<action_name>:<priority>:<xpath>],[...]"
|
80
|
+
DESC
|
81
|
+
|
82
|
+
first_argument :controller_name, :required => true, :desc => "Name of the Controller."
|
83
|
+
second_argument :actions_arg, :required => true, :as => :array, :default => [], :desc => "Actions implemented by this controller. Use the following syntax : name:priority:xpath"
|
84
|
+
|
85
|
+
def self.source_root
|
86
|
+
File.join(File.dirname(__FILE__), '../../templates/babylon/app/controllers')
|
87
|
+
end
|
88
|
+
|
89
|
+
def controller_actions
|
90
|
+
@controller_actions ||= actions_arg.map { |a| a.split(":") }
|
91
|
+
end
|
92
|
+
|
93
|
+
def controller_class_name
|
94
|
+
"#{controller_name.capitalize}Controller"
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# This is a hack since Templater doesn't offer any simple way to edit files right now...
|
99
|
+
def add_route_for_actions_in_controller(actions, controller)
|
100
|
+
sentinel = "Babylon.router.draw do"
|
101
|
+
router_path = "config/routes.rb"
|
102
|
+
actions.each do |action|
|
103
|
+
to_inject = "xpath(\"#{action[2]}\").to(:controller => \"#{controller}\", :action => \"#{action[0]}\").priority(#{action[1]})"
|
104
|
+
if File.exist?(router_path)
|
105
|
+
content = File.read(router_path).gsub(/(#{Regexp.escape(sentinel)})/mi){|match| "#{match}\n\t#{to_inject}"}
|
106
|
+
File.open(router_path, 'wb') { |file| file.write(content) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
template :controller do |t|
|
112
|
+
t.source = "#{source_root}/controller.rb"
|
113
|
+
t.destination = "app/controllers/#{controller_name}_controller.rb"
|
114
|
+
self.add_route_for_actions_in_controller(controller_actions, controller_name)
|
115
|
+
# This is a hack since Templater doesn't offer any simple way to write several files from one...
|
116
|
+
FileUtils.mkdir("app/views/#{controller_name}") unless File.exists?("app/views/#{controller_name}")
|
117
|
+
controller_actions.each do |action|
|
118
|
+
FileUtils.cp("#{source_root}/../views/view.rb", "app/views/#{controller_name}/#{action[0]}.xml.builder")
|
119
|
+
end
|
120
|
+
|
121
|
+
# And now, let's create the stanza files
|
122
|
+
controller_actions.each do |action|
|
123
|
+
FileUtils.cp("#{source_root}/../stanzas/stanza.rb", "app/stanzas/#{action[0]}.rb")
|
124
|
+
# We need to replace
|
125
|
+
# "class Stanza < Babylon::Base::Stanza" with "class #{action[0]} < Babylon::Base::Stanza"
|
126
|
+
content = File.read("app/stanzas/#{action[0]}.rb").gsub("class Stanza < Babylon::Base::Stanza", "class #{action[0].capitalize} < Babylon::Base::Stanza")
|
127
|
+
File.open("app/stanzas/#{action[0]}.rb", 'wb') { |file| file.write(content) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# The generators are added to the manifold, and assigned the names 'wiki' and 'blog'.
|
133
|
+
# So you can call them <script name> blog merb-blog-in-10-minutes and
|
134
|
+
# <script name> blog merb-wiki-in-10-minutes, respectively
|
135
|
+
add :application, ApplicationGenerator
|
136
|
+
add :controller, ControllerGenerator
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|