noah 0.0.5-jruby → 0.1-jruby
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +10 -0
- data/LICENSE +201 -0
- data/README.md +68 -212
- data/Rakefile +65 -41
- data/TODO.md +65 -0
- data/bin/noah +2 -1
- data/bin/noah-watcher.rb +103 -0
- data/config.ru +6 -3
- data/config/warble.rb +18 -0
- data/examples/README.md +116 -0
- data/examples/cluster.ru +2 -0
- data/examples/custom-watcher.rb +10 -0
- data/examples/httpclient-server.rb +7 -0
- data/examples/httpclient.rb +12 -0
- data/examples/httpclient2.rb +28 -0
- data/examples/js/FABridge.js +1452 -0
- data/examples/js/WebSocketMain.swf +830 -0
- data/examples/js/swfobject.js +851 -0
- data/examples/js/web_socket.js +312 -0
- data/examples/logger.rb +11 -0
- data/examples/reconfiguring-sinatra-watcher.rb +11 -0
- data/examples/reconfiguring-sinatra.rb +33 -0
- data/examples/simple-post.rb +17 -0
- data/examples/websocket.html +24 -0
- data/examples/websocket.rb +41 -0
- data/lib/noah.rb +6 -8
- data/lib/noah/app.rb +20 -268
- data/lib/noah/application_routes.rb +70 -0
- data/lib/noah/ark.rb +0 -0
- data/lib/noah/configuration_routes.rb +81 -0
- data/lib/noah/custom_watcher.rb +79 -0
- data/lib/noah/ephemeral_routes.rb +47 -0
- data/lib/noah/helpers.rb +37 -14
- data/lib/noah/host_routes.rb +69 -0
- data/lib/noah/models.rb +86 -5
- data/lib/noah/models/applications.rb +41 -0
- data/lib/noah/models/configurations.rb +49 -0
- data/lib/noah/models/ephemerals.rb +54 -0
- data/lib/noah/models/hosts.rb +56 -0
- data/lib/noah/models/services.rb +54 -0
- data/lib/noah/models/watchers.rb +62 -0
- data/lib/noah/passthrough.rb +11 -0
- data/lib/noah/service_routes.rb +71 -0
- data/lib/noah/validations.rb +1 -0
- data/lib/noah/validations/watcher_validations.rb +48 -0
- data/lib/noah/version.rb +1 -1
- data/lib/noah/watcher_routes.rb +45 -0
- data/noah.gemspec +25 -17
- data/spec/application_spec.rb +30 -30
- data/spec/configuration_spec.rb +78 -14
- data/spec/ephemeral_spec.rb +59 -0
- data/spec/host_spec.rb +21 -21
- data/spec/noahapp_application_spec.rb +6 -6
- data/spec/noahapp_configuration_spec.rb +5 -5
- data/spec/noahapp_ephemeral_spec.rb +115 -0
- data/spec/noahapp_host_spec.rb +3 -3
- data/spec/noahapp_service_spec.rb +10 -10
- data/spec/noahapp_watcher_spec.rb +123 -0
- data/spec/service_spec.rb +27 -27
- data/spec/spec_helper.rb +13 -22
- data/spec/support/db/.keep +0 -0
- data/spec/support/test-redis.conf +8 -0
- data/spec/watcher_spec.rb +62 -0
- data/views/index.haml +21 -15
- metadata +189 -146
- data/Gemfile.lock +0 -83
- data/doc/coverage/index.html +0 -138
- data/doc/coverage/jquery-1.3.2.min.js +0 -19
- data/doc/coverage/jquery.tablesorter.min.js +0 -15
- data/doc/coverage/lib-helpers_rb.html +0 -393
- data/doc/coverage/lib-models_rb.html +0 -1449
- data/doc/coverage/noah_rb.html +0 -2019
- data/doc/coverage/print.css +0 -12
- data/doc/coverage/rcov.js +0 -42
- data/doc/coverage/screen.css +0 -270
- data/lib/noah/applications.rb +0 -46
- data/lib/noah/configurations.rb +0 -49
- data/lib/noah/hosts.rb +0 -54
- data/lib/noah/services.rb +0 -57
- data/lib/noah/watchers.rb +0 -18
@@ -0,0 +1,54 @@
|
|
1
|
+
module Noah
|
2
|
+
class Ephemeral < Model
|
3
|
+
|
4
|
+
attribute :path
|
5
|
+
attribute :data
|
6
|
+
attribute :lifetime
|
7
|
+
|
8
|
+
index :path
|
9
|
+
|
10
|
+
def validate
|
11
|
+
super
|
12
|
+
assert_present :path
|
13
|
+
assert_unique :path
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
@name = path
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
h = {:path => path, :data => data, :created_at => created_at, :updated_at => updated_at}
|
22
|
+
super.merge(h)
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def find_or_create(opts = {})
|
27
|
+
begin
|
28
|
+
path, data = opts[:path], opts[:data]
|
29
|
+
find(:path => path).first.nil? ? (eph = new(:path => path)) : (eph = find(:path => path).first)
|
30
|
+
eph.data = data
|
31
|
+
if eph.valid?
|
32
|
+
eph.save
|
33
|
+
end
|
34
|
+
eph
|
35
|
+
rescue Exception => e
|
36
|
+
e.message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def save_hook
|
43
|
+
# called after any create,update,delete
|
44
|
+
# logic needed to expire any orphaned ephemerals
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def path_protected?(path_part)
|
49
|
+
# Check for protected paths in ephemeral nodes
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'services')
|
2
|
+
module Noah
|
3
|
+
class Host < Model
|
4
|
+
# Host model
|
5
|
+
# @return {Host} a {Host} object
|
6
|
+
|
7
|
+
attribute :name
|
8
|
+
attribute :status
|
9
|
+
collection :services, Service
|
10
|
+
|
11
|
+
index :name
|
12
|
+
index :status
|
13
|
+
|
14
|
+
def validate
|
15
|
+
super
|
16
|
+
assert_present :name
|
17
|
+
assert_present :status
|
18
|
+
assert_unique :name
|
19
|
+
assert_member :status, ["up","down","pending"]
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Hash] A hash representation of a {Host}
|
23
|
+
def to_hash
|
24
|
+
arr = []
|
25
|
+
services.sort.each {|s| arr << s.to_hash}
|
26
|
+
h = {:name => name, :status => status, :created_at => created_at, :updated_at => updated_at, :services => arr}
|
27
|
+
super.merge(h)
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def find_or_create(opts = {})
|
32
|
+
begin
|
33
|
+
# exclude requested status from lookup
|
34
|
+
h = find(opts.reject{|key,value| key == :status}).first
|
35
|
+
host = h.nil? ? create(opts) : h
|
36
|
+
host.status = opts[:status]
|
37
|
+
if host.valid?
|
38
|
+
host.save
|
39
|
+
end
|
40
|
+
host
|
41
|
+
rescue Exception => e
|
42
|
+
e.message
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class Hosts
|
50
|
+
# @param [Hash] optional filters for results
|
51
|
+
# @return [Array] Array of {Host} objects
|
52
|
+
def self.all(options = {})
|
53
|
+
options.empty? ? Noah::Host.all.sort : Noah::Host.find(options).sort
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Noah
|
2
|
+
|
3
|
+
class Service < Model
|
4
|
+
|
5
|
+
attribute :name
|
6
|
+
attribute :status
|
7
|
+
reference :host, Host
|
8
|
+
|
9
|
+
index :name
|
10
|
+
index :status
|
11
|
+
|
12
|
+
def validate
|
13
|
+
super
|
14
|
+
assert_present :name
|
15
|
+
assert_present :status
|
16
|
+
assert_present :host_id
|
17
|
+
assert_unique [:name, :host_id]
|
18
|
+
assert_member :status, ["up", "down", "pending"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
Host[host_id].nil? ? host_name=nil : host_name=Host[host_id].name
|
23
|
+
super.merge(:name => name, :status => status, :updated_at => updated_at, :host => host_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def find_or_create(opts = {})
|
28
|
+
begin
|
29
|
+
# convert passed host object to host_id if passed
|
30
|
+
if opts.has_key?(:host)
|
31
|
+
opts.merge!({:host_id => opts[:host].id})
|
32
|
+
opts.reject!{|key, value| key == :host}
|
33
|
+
end
|
34
|
+
# exclude requested status from lookup
|
35
|
+
s = find(opts.reject{|key,value| key == :status}).first
|
36
|
+
service = s.nil? ? create(opts) : s
|
37
|
+
service.status = opts[:status]
|
38
|
+
if service.valid?
|
39
|
+
service.save
|
40
|
+
end
|
41
|
+
service
|
42
|
+
rescue Exception => e
|
43
|
+
e.message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Services
|
50
|
+
def self.all(options = {})
|
51
|
+
options.empty? ? Service.all.sort : Service.find(options).sort
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
module Noah
|
3
|
+
class Watcher < Model #NYI
|
4
|
+
# Don't trust anything in here yet
|
5
|
+
# I'm still trying a few things
|
6
|
+
include WatcherValidations
|
7
|
+
|
8
|
+
attribute :pattern
|
9
|
+
attribute :endpoint
|
10
|
+
attribute :name
|
11
|
+
|
12
|
+
index :pattern
|
13
|
+
index :endpoint
|
14
|
+
|
15
|
+
def validate
|
16
|
+
super
|
17
|
+
assert_present :endpoint
|
18
|
+
assert_present :pattern
|
19
|
+
assert_unique [:endpoint, :pattern]
|
20
|
+
assert_not_superset
|
21
|
+
assert_not_subset
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
@name = Base64.encode64("#{pattern}|#{endpoint}").gsub("\n","")
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
h = {:pattern => pattern, :name => name, :endpoint => endpoint, :created_at => created_at, :updated_at => updated_at}
|
30
|
+
super.merge(h)
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def find_by_name(name)
|
35
|
+
pattern, endpoint = Base64.decode64(name).split('|')
|
36
|
+
find(:pattern => pattern, :endpoint => endpoint).first
|
37
|
+
end
|
38
|
+
|
39
|
+
def watch_list
|
40
|
+
arr = []
|
41
|
+
watches = self.all.sort_by(:pattern)
|
42
|
+
watches.each {|w| arr << w.name}
|
43
|
+
arr
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
# Not sure about these next two.
|
49
|
+
# Could get around patterns changing due to namespace changes
|
50
|
+
def path_to_pattern
|
51
|
+
end
|
52
|
+
|
53
|
+
def pattern_to_path
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Watchers
|
58
|
+
def self.all(options = {})
|
59
|
+
options.empty? ? Watcher.all.sort : Watcher.find(options).sort
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Noah::App
|
2
|
+
# Service URIs
|
3
|
+
|
4
|
+
# get named {Service} for named {Host}
|
5
|
+
get '/s/:servicename/:hostname/?' do |servicename, hostname|
|
6
|
+
hs = host_service(hostname, servicename)
|
7
|
+
if hs.nil?
|
8
|
+
halt 404
|
9
|
+
else
|
10
|
+
hs.to_json
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/s/:servicename/?' do |servicename|
|
15
|
+
s = services(:name => servicename)
|
16
|
+
s.map {|x| x.to_hash}
|
17
|
+
if s.empty?
|
18
|
+
halt 404
|
19
|
+
else
|
20
|
+
s.to_json
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/s/?' do
|
25
|
+
if services.empty?
|
26
|
+
halt 404
|
27
|
+
else
|
28
|
+
services.map {|s| s.to_hash}
|
29
|
+
services.to_json
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
put '/s/:servicename/watch' do |servicename|
|
34
|
+
required_params = ["endpoint"]
|
35
|
+
data = JSON.parse(request.body.read)
|
36
|
+
(data.keys.sort == required_params.sort) ? (s = Noah::Service.find(:name => servicename).first) : (raise "Missing Parameters")
|
37
|
+
s.nil? ? (halt 404) : (w = s.watch!(:endpoint => data['endpoint']))
|
38
|
+
w.to_json
|
39
|
+
end
|
40
|
+
|
41
|
+
put '/s/:servicename/?' do |servicename|
|
42
|
+
required_params = ["status", "host", "name"]
|
43
|
+
data = JSON.parse(request.body.read)
|
44
|
+
if data.keys.sort == required_params.sort
|
45
|
+
h = Noah::Host.find(:name => data['host']).first || (raise "Invalid Host")
|
46
|
+
service = Noah::Service.find_or_create(:name => servicename, :status => data['status'], :host => h)
|
47
|
+
if service.valid?
|
48
|
+
action = service.is_new? ? "create" : "update"
|
49
|
+
service.save
|
50
|
+
r = {"action" => action, "result" => "success", "id" => service.id, "host" => h.name, "name" => service.name}
|
51
|
+
r.to_json
|
52
|
+
else
|
53
|
+
raise "#{format_errors(service)}"
|
54
|
+
end
|
55
|
+
else
|
56
|
+
raise "Missing Parameters"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
delete '/s/:servicename/:hostname/?' do |servicename, hostname|
|
61
|
+
host = Noah::Host.find(:name => hostname).first || (halt 404)
|
62
|
+
service = Noah::Service.find(:name => servicename, :host_id => host.id).first || (halt 404)
|
63
|
+
if host && service
|
64
|
+
service.delete
|
65
|
+
r = {"action" => "delete", "result" => "success", "id" => service.id, "host" => host.name, "service" => servicename}
|
66
|
+
r.to_json
|
67
|
+
else
|
68
|
+
halt 404
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'validations','watcher_validations')
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Noah
|
2
|
+
module WatcherValidations
|
3
|
+
|
4
|
+
def assert_not_subset(error = [:pattern, :already_provided])
|
5
|
+
self.instance_of?(Noah::Watcher) ? (assert endpoint_covered?, error) : (assert false, "Validation not applicable")
|
6
|
+
end
|
7
|
+
|
8
|
+
def assert_not_superset(error = [:pattern, :replaces_existing])
|
9
|
+
self.instance_of?(Noah::Watcher) ? (assert endpoint_overrides?, error) : (assert false, "Validation not applicable")
|
10
|
+
end
|
11
|
+
|
12
|
+
def assert_valid_watch(error = [:pattern, :invalid_format])
|
13
|
+
self.instance_of?(Noah::Watcher) ? (assert pattern_valid?, error) : (assert false, "Validation not applicable")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def endpoint_covered?
|
18
|
+
watches = Watcher.all.find(:endpoint => self.endpoint).sort
|
19
|
+
watches.each do |w|
|
20
|
+
if (w.pattern.size < self.pattern.size) && self.pattern.match(/^#{w.pattern}/)
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
rescue ArgumentError
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
def endpoint_overrides?
|
29
|
+
watches = Watcher.all.find(:endpoint => self.endpoint).sort
|
30
|
+
watches.each do |w|
|
31
|
+
if (w.pattern.size > self.pattern.size) && w.pattern.match(/^#{self.pattern}/)
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue ArgumentError
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
|
39
|
+
def pattern_valid?
|
40
|
+
unless self.pattern.match(/^\/\/noah\/.*\/$/)
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
rescue ArgumentError
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/noah/version.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
class Noah::App
|
2
|
+
|
3
|
+
get '/w/:name' do |name|
|
4
|
+
w = Noah::Watcher.find_by_name(name)
|
5
|
+
w.nil? ? (halt 404) : w.to_json
|
6
|
+
end
|
7
|
+
|
8
|
+
get '/w/?' do
|
9
|
+
w = Noah::Watcher.all.sort_by(:pattern)
|
10
|
+
if w.size == 0
|
11
|
+
halt 404
|
12
|
+
else
|
13
|
+
w.to_json
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
put '/w/?' do
|
18
|
+
required_params = %w[endpoint pattern]
|
19
|
+
data = JSON.parse(request.body.read)
|
20
|
+
(data.keys.sort == required_params.sort) ? (pattern, endpoint = data['pattern'],data['endpoint']) : (raise "Missing Parameters")
|
21
|
+
w = Noah::Watcher.create(:pattern => pattern, :endpoint => endpoint)
|
22
|
+
if w.valid?
|
23
|
+
w.save
|
24
|
+
r = {"action" => "create", "result" => "success"}.merge(w.to_hash)
|
25
|
+
r.to_json
|
26
|
+
else
|
27
|
+
raise "#{format_errors(w)}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
delete '/w/?' do
|
32
|
+
required_params = %w[endpoint pattern]
|
33
|
+
data = JSON.parse(request.body.read)
|
34
|
+
(data.keys.sort == required_params.sort) ? (pattern, endpoint = data['pattern'],data['endpoint']) : (raise "Missing Parameters")
|
35
|
+
w = Noah::Watcher.find(:pattern => pattern, :endpoint => endpoint).first
|
36
|
+
if w.nil?
|
37
|
+
halt 404
|
38
|
+
else
|
39
|
+
w.delete
|
40
|
+
r = {"result" => "success", "action" => "delete"}.merge(w.to_hash)
|
41
|
+
r.to_json
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/noah.gemspec
CHANGED
@@ -5,8 +5,9 @@ require "noah/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "noah"
|
7
7
|
s.version = Noah::VERSION
|
8
|
-
s.platform =
|
9
|
-
s.
|
8
|
+
#s.platform = Gem::Platform::RUBY
|
9
|
+
s.platform = "jruby"
|
10
|
+
s.authors = ["John E. Vincent"]
|
10
11
|
s.email = ["lusis.org+rubygems.org@gmail.com"]
|
11
12
|
s.homepage = "https://github.com/lusis/noah"
|
12
13
|
s.summary = %q{Application registry based on Apache Zookeeper}
|
@@ -19,26 +20,33 @@ Gem::Specification.new do |s|
|
|
19
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
21
|
s.require_paths = ["lib"]
|
21
22
|
|
22
|
-
s.add_dependency("
|
23
|
+
s.add_dependency("eventmachine", ["1.0.0.beta.3"])
|
24
|
+
s.add_dependency("em-http-request", ["1.0.0.beta.3"])
|
25
|
+
s.add_dependency("redis", ["= 2.1.1"])
|
26
|
+
s.add_dependency("nest", ["= 1.1.0"])
|
27
|
+
s.add_dependency("rack", ["= 1.2.1"])
|
28
|
+
s.add_dependency("tilt", ["= 1.2.2"])
|
23
29
|
s.add_dependency("sinatra", ["= 1.1.2"])
|
24
|
-
s.add_dependency("sinatra-namespace", ["0.6.1"])
|
25
30
|
s.add_dependency("ohm", ["= 0.1.3"])
|
26
|
-
s.add_dependency("ohm-contrib", ["= 0.1.
|
31
|
+
s.add_dependency("ohm-contrib", ["= 0.1.1"])
|
27
32
|
s.add_dependency("haml", ["= 3.0.25"])
|
28
33
|
s.add_dependency("vegas", ["= 0.1.8"])
|
29
|
-
s.add_dependency("yajl-ruby", ["= 0.7.9"]) if s.platform.to_s == 'ruby'
|
30
|
-
s.add_dependency("jruby-json", ["= 1.5.0"]) if s.platform.to_s == 'jruby'
|
31
|
-
s.add_dependency("thin", ["= 1.2.7"]) if s.platform.to_s == 'ruby'
|
32
|
-
s.add_dependency("json-jruby", ["= 1.4.6"]) if s.platform.to_s == 'jruby'
|
33
|
-
s.add_dependency("jruby-openssl", ["= 0.7.3"]) if s.platform.to_s == 'jruby'
|
34
34
|
|
35
|
+
|
36
|
+
if RUBY_PLATFORM =~ /java/
|
37
|
+
s.add_dependency("jruby-openssl")
|
38
|
+
s.add_dependency("json")
|
39
|
+
s.add_development_dependency("warbler", ["= 1.2.1"])
|
40
|
+
else
|
41
|
+
s.add_dependency("hiredis", ["= 0.3.1"])
|
42
|
+
s.add_dependency("yajl-ruby")
|
43
|
+
s.add_dependency("SystemTimer") if RUBY_VERSION =~ /1.8/
|
44
|
+
s.add_dependency("thin")
|
45
|
+
end
|
46
|
+
|
47
|
+
s.add_development_dependency("diff-lcs", ["= 1.1.2"])
|
35
48
|
s.add_development_dependency("sinatra-reloader", ["= 0.5.0"])
|
36
|
-
s.add_development_dependency("rspec", ["
|
37
|
-
s.add_development_dependency("rcov", ["= 0.9.9"])
|
49
|
+
s.add_development_dependency("rspec", ["~> 2.5"])
|
50
|
+
s.add_development_dependency("rcov", ["= 0.9.9"])
|
38
51
|
s.add_development_dependency("rack-test", ["= 0.5.7"])
|
39
|
-
s.add_development_dependency("ZenTest", ["= 4.4.2"])
|
40
|
-
s.add_development_dependency("autotest", ["= 4.4.6"])
|
41
|
-
s.add_development_dependency("autotest-growl", ["= 0.2.9"])
|
42
|
-
s.add_development_dependency("warbler", ["= 1.2.1"]) if s.platform.to_s == 'java'
|
43
|
-
|
44
52
|
end
|