noah 0.0.5-jruby → 0.1-jruby
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|