tronprint 1.2.0 → 1.2.1
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/lib/tronprint.rb +13 -2
- data/lib/tronprint/aggregator.rb +25 -2
- data/lib/tronprint/cpu_monitor.rb +23 -10
- data/lib/tronprint/rails/tronprint_helper.rb +30 -24
- data/lib/tronprint/version.rb +1 -1
- data/spec/tronprint/aggregator_spec.rb +16 -0
- data/spec/tronprint/cpu_monitor_spec.rb +26 -1
- data/spec/tronprint/rails/tronprint_helper_spec.rb +14 -0
- data/spec/tronprint_spec.rb +25 -0
- data/tronprint.gemspec +3 -3
- metadata +27 -27
data/lib/tronprint.rb
CHANGED
@@ -82,12 +82,20 @@ module Tronprint
|
|
82
82
|
|
83
83
|
# The Tronprint::Aggregator instance.
|
84
84
|
def aggregator
|
85
|
-
@aggregator
|
85
|
+
return @aggregator unless @aggregator.nil?
|
86
|
+
begin
|
87
|
+
@aggregator = Aggregator.new aggregator_options.dup
|
88
|
+
rescue => e
|
89
|
+
backtrace = ([e] + e.backtrace).join("\n")
|
90
|
+
log_error "Failed to connect to aggregator: #{backtrace}"
|
91
|
+
@aggregator = nil
|
92
|
+
end
|
93
|
+
@aggregator
|
86
94
|
end
|
87
95
|
|
88
96
|
# The Tronprint::CPUMonitor instance.
|
89
97
|
def cpu_monitor
|
90
|
-
@cpu_monitor ||= CPUMonitor.new
|
98
|
+
@cpu_monitor ||= CPUMonitor.new application_name
|
91
99
|
end
|
92
100
|
|
93
101
|
# The Tronprint::TrafficMonitor instance
|
@@ -158,4 +166,7 @@ module Tronprint
|
|
158
166
|
def log_info(message)
|
159
167
|
logger.info message if logger
|
160
168
|
end
|
169
|
+
def log_error(message)
|
170
|
+
logger.error message if logger
|
171
|
+
end
|
161
172
|
end
|
data/lib/tronprint/aggregator.rb
CHANGED
@@ -21,6 +21,9 @@ module Tronprint
|
|
21
21
|
self.adapter = options.delete :adapter
|
22
22
|
self.adapter ||= 'pstore'
|
23
23
|
self.adapter = self.adapter.to_s.downcase
|
24
|
+
|
25
|
+
options = process_options(options)
|
26
|
+
|
24
27
|
begin
|
25
28
|
require "moneta/#{self.adapter}"
|
26
29
|
klass = Moneta.const_get adapter_constant
|
@@ -34,6 +37,14 @@ module Tronprint
|
|
34
37
|
super instance
|
35
38
|
end
|
36
39
|
|
40
|
+
# Handle initializer options for some special cases
|
41
|
+
def process_options(options)
|
42
|
+
if adapter == 'mongodb'
|
43
|
+
options[:pool_size] = 3
|
44
|
+
end
|
45
|
+
options
|
46
|
+
end
|
47
|
+
|
37
48
|
def __getobj__ # :nodoc:
|
38
49
|
@delegate_sd_obj
|
39
50
|
end
|
@@ -133,10 +144,22 @@ module Tronprint
|
|
133
144
|
end
|
134
145
|
|
135
146
|
def update_entry(key, value)
|
136
|
-
|
147
|
+
begin
|
148
|
+
old_value = self[key]
|
149
|
+
rescue => e
|
150
|
+
backtrace = ([e] + e.backtrace).join("\n")
|
151
|
+
Tronprint.log_error "Tronprint: Unable to fetch statistics from datastore: #{backtrace}"
|
152
|
+
end
|
153
|
+
|
137
154
|
new_value = old_value ? old_value + value : value
|
138
155
|
Tronprint.log_debug "Tronprint.aggregator[#{key.inspect}] = #{new_value}"
|
139
|
-
|
156
|
+
|
157
|
+
begin
|
158
|
+
self[key] = new_value
|
159
|
+
rescue => e
|
160
|
+
backtrace = ([e] + e.backtrace).join("\n")
|
161
|
+
Tronprint.log_error "Tronprint: Unable to update datastore with statistics: #{backtrace}"
|
162
|
+
end
|
140
163
|
end
|
141
164
|
end
|
142
165
|
end
|
@@ -2,34 +2,47 @@ module Tronprint
|
|
2
2
|
|
3
3
|
# Tronprint::CPUMonitor is a thread that monitors aggregate CPU usage.
|
4
4
|
class CPUMonitor < Thread
|
5
|
-
attr_accessor :total_recorded_cpu_time, :
|
5
|
+
attr_accessor :total_recorded_cpu_time, :application_name, :run_continuously
|
6
6
|
|
7
7
|
# Parameters:
|
8
|
-
# +aggregator+:: A Tronprint::Aggregator instance.
|
9
8
|
# +application_name+:: A unique application name.
|
10
9
|
# +options+:: Extra options, such as:
|
11
10
|
# --+run+:: Whether to fork the thread and run continuously. +true+ by default.
|
12
|
-
def initialize(
|
13
|
-
self.aggregator = aggregator
|
11
|
+
def initialize(application_name, options = {})
|
14
12
|
self.application_name = application_name
|
15
13
|
options[:run] = true if options[:run].nil?
|
16
14
|
if options[:run]
|
17
|
-
super
|
18
|
-
while(true) do
|
19
|
-
monitor
|
20
|
-
sleep(5)
|
21
|
-
end
|
22
|
-
end
|
15
|
+
super &method(:thread_loop)
|
23
16
|
else
|
24
17
|
super() {} # Ruby 1.8 hack
|
25
18
|
end
|
26
19
|
end
|
27
20
|
|
21
|
+
def run_continuously?
|
22
|
+
@run_continuously = true if @run_continuously.nil?
|
23
|
+
@run_continuously
|
24
|
+
end
|
25
|
+
|
26
|
+
# The main thread loop, monitors CPU usage every 5 seconds if
|
27
|
+
# a connection to the aggregator is available
|
28
|
+
def thread_loop
|
29
|
+
first_run = true
|
30
|
+
begin
|
31
|
+
monitor if aggregator
|
32
|
+
sleep(5) if run_continuously?
|
33
|
+
first_run = false
|
34
|
+
end while(first_run or run_continuously?)
|
35
|
+
end
|
36
|
+
|
28
37
|
# The key used to store CPU data in the Aggregator.
|
29
38
|
def key
|
30
39
|
[application_name, 'application', 'cpu_time'].join('/')
|
31
40
|
end
|
32
41
|
|
42
|
+
def aggregator
|
43
|
+
Tronprint.aggregator
|
44
|
+
end
|
45
|
+
|
33
46
|
# The process used to collect CPU uptime data and store it.
|
34
47
|
def monitor
|
35
48
|
elapsed_time = elapsed_cpu_time
|
@@ -21,31 +21,37 @@ module TronprintHelper
|
|
21
21
|
|
22
22
|
# An informational badge displaying total energy, footprint, CO2/minute
|
23
23
|
def footprint_badge(options = {})
|
24
|
-
|
24
|
+
begin
|
25
|
+
footprint = pounds_with_precision total_estimate
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
two_hr_emissions = Tronprint.statistics.
|
28
|
+
emission_estimate(Time.now - 7200, Time.now).to_f
|
29
|
+
rate = two_hr_emissions / 120 # kgs CO2 per minute over last 2 hours
|
30
|
+
rate = rate < 0.0001 ? "< 0.0001" : pounds_with_precision(rate)
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
32
|
+
text = <<-HTML
|
33
|
+
<p class="tronprint-footprint">
|
34
|
+
<span class="tronprint-total-footprint">
|
35
|
+
<span class="tronprint-label">Total app footprint:</span>
|
36
|
+
<span class="tronprint-value">#{total_electricity.to_i}</span>
|
37
|
+
<span class="tronprint-units">W</span>,
|
38
|
+
<span class="tronprint-value">#{footprint}</span>
|
39
|
+
<span class="tronprint-units">lbs. CO<sub>2</sub>e</span>
|
40
|
+
</span>
|
41
|
+
<span class="tronprint-separator">·</span>
|
42
|
+
<span class="tronprint-current-footprint">
|
43
|
+
<span class="tronprint-label">Current footprint:</span>
|
44
|
+
<span class="tronprint-value">#{rate}</span>
|
45
|
+
<span class="tronprint-units">lbs. CO<sub>2</sub>e/min.</span>
|
46
|
+
</span>
|
47
|
+
<span class="tronprint-attribution">#{tronprint_attribution if options[:attribution]}</span>
|
48
|
+
</p>
|
49
|
+
HTML
|
50
|
+
rescue => e
|
51
|
+
text = <<-HTML
|
52
|
+
<p class="tronprint-footprint">App footprint unavailable</p>
|
53
|
+
HTML
|
54
|
+
end
|
49
55
|
|
50
56
|
text.html_safe
|
51
57
|
end
|
@@ -57,7 +63,7 @@ module TronprintHelper
|
|
57
63
|
|
58
64
|
# A link to more information about Tronprint
|
59
65
|
def tronprint_badge
|
60
|
-
%Q{<p class="tronprint-link"
|
66
|
+
%Q{<p class="tronprint-link">#{tronprint_attribution}</p>}.html_safe
|
61
67
|
end
|
62
68
|
|
63
69
|
# Let the world know that your app is powered by CM1
|
data/lib/tronprint/version.rb
CHANGED
@@ -73,6 +73,22 @@ describe Tronprint::Aggregator do
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
+
describe '#process_options' do
|
77
|
+
it 'sets a pool size if the adapter is mongodb' do
|
78
|
+
aggregator.adapter = 'mongodb'
|
79
|
+
options = aggregator.process_options :hostname => 'example.com'
|
80
|
+
options[:pool_size].should > 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#update_entry' do
|
85
|
+
it 'logs any connection errors' do
|
86
|
+
aggregator.stub!(:[]).and_raise StandardError
|
87
|
+
Tronprint.should_receive(:log_error).once
|
88
|
+
aggregator.send :update_entry, 'foo', 77
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
76
92
|
describe '#range_total' do
|
77
93
|
before :each do
|
78
94
|
[
|
@@ -2,7 +2,32 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Tronprint::CPUMonitor do
|
4
4
|
let(:aggregator) { Tronprint::Aggregator.new :adapter => :memory }
|
5
|
-
let(:cpu_monitor) { Tronprint::CPUMonitor.new
|
5
|
+
let(:cpu_monitor) { Tronprint::CPUMonitor.new 'my_app', :run => false }
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
Tronprint.stub!(:aggregator).and_return aggregator
|
9
|
+
cpu_monitor.run_continuously = false
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#thread_loop' do
|
13
|
+
it 'monitors CPU usage' do
|
14
|
+
cpu_monitor.should_receive :monitor
|
15
|
+
cpu_monitor.thread_loop
|
16
|
+
end
|
17
|
+
it 'does not monitor if no aggregator is available' do
|
18
|
+
cpu_monitor.should_not_receive :monitor
|
19
|
+
Tronprint.stub!(:aggregator)
|
20
|
+
cpu_monitor.thread_loop
|
21
|
+
end
|
22
|
+
it 'eventually monitors when it can connect to an aggregator' do
|
23
|
+
cpu_monitor.stub!(:sleep)
|
24
|
+
cpu_monitor.stub!(:run_continuously?).and_return(true, true, true, true, true, false)
|
25
|
+
Tronprint.stub!(:aggregator).and_return(nil, nil, aggregator)
|
26
|
+
|
27
|
+
cpu_monitor.should_receive :monitor
|
28
|
+
cpu_monitor.thread_loop
|
29
|
+
end
|
30
|
+
end
|
6
31
|
|
7
32
|
describe '#monitor' do
|
8
33
|
before :each do
|
@@ -14,5 +14,19 @@ describe TronprintHelper do
|
|
14
14
|
helper.total_footprint.should == 89.4
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
describe '#footprint_badge' do
|
19
|
+
it 'outputs a badge' do
|
20
|
+
helper.stub!(:pounds_with_precision).and_return "100.0"
|
21
|
+
helper.stub!(:total_estimate).and_return 100.0
|
22
|
+
Tronprint.statistics.stub!(:emission_estimate).and_return 100.0
|
23
|
+
helper.stub!(:total_electricity).and_return 20
|
24
|
+
helper.footprint_badge.should =~ /Total app footprint/
|
25
|
+
end
|
26
|
+
it 'gracefully handles lost aggregator connections' do
|
27
|
+
helper.stub!(:total_estimate).and_raise StandardError
|
28
|
+
helper.footprint_badge.should =~ /App footprint unavailable/
|
29
|
+
end
|
30
|
+
end
|
17
31
|
end
|
18
32
|
|
data/spec/tronprint_spec.rb
CHANGED
@@ -37,6 +37,12 @@ describe Tronprint do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
describe '.traffic_monitor' do
|
41
|
+
it 'returns a traffic monitor' do
|
42
|
+
Tronprint.traffic_monitor.should be_a(Tronprint::TrafficMonitor)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
40
46
|
describe '.config' do
|
41
47
|
it 'returns a configuration loaded from disk' do
|
42
48
|
Tronprint.stub!(:load_config).and_return :foo => :bar
|
@@ -89,6 +95,25 @@ describe Tronprint do
|
|
89
95
|
end
|
90
96
|
end
|
91
97
|
|
98
|
+
describe '.aggregator' do
|
99
|
+
it 'returns an aggregator' do
|
100
|
+
Tronprint.aggregator.should be_a(Tronprint::Aggregator)
|
101
|
+
end
|
102
|
+
context 'when the aggregator fails to load a datastore' do
|
103
|
+
before :each do
|
104
|
+
Tronprint::Aggregator.stub!(:new).and_raise(StandardError)
|
105
|
+
Tronprint.instance_variable_set :@aggregator, nil
|
106
|
+
end
|
107
|
+
it 'logs an error' do
|
108
|
+
Tronprint.should_receive :log_error
|
109
|
+
Tronprint.aggregator
|
110
|
+
end
|
111
|
+
it 'returns nil' do
|
112
|
+
Tronprint.aggregator.should be_nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
92
117
|
describe '.aggregator_options' do
|
93
118
|
before :each do
|
94
119
|
ENV['MONGOHQ_URL'] = nil
|
data/tronprint.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Tronprint::VERSION
|
8
8
|
|
9
9
|
s.authors = ['Derek Kastner']
|
10
|
-
s.date = "2011-07-
|
10
|
+
s.date = "2011-07-20"
|
11
11
|
s.description = %q{A gem for monitoring the carbon footprint of your ruby app}
|
12
12
|
s.email = %q{dkastner@gmail.com}
|
13
13
|
s.homepage = %q{http://github.com/brighterplanet/tronprint}
|
@@ -38,8 +38,8 @@ Gem::Specification.new do |s|
|
|
38
38
|
s.add_development_dependency 'rspec', '~> 2.0'
|
39
39
|
s.add_development_dependency 'sandbox'
|
40
40
|
s.add_development_dependency 'timecop'
|
41
|
-
s.add_runtime_dependency
|
41
|
+
s.add_runtime_dependency 'carbon', '~> 1.1.1'
|
42
42
|
s.add_runtime_dependency 'i18n'
|
43
|
-
s.add_runtime_dependency 'dkastner-moneta', '~> 1.1.
|
43
|
+
s.add_runtime_dependency 'dkastner-moneta', '~> 1.1.1'
|
44
44
|
end
|
45
45
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tronprint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-07-
|
12
|
+
date: 2011-07-20 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
16
|
-
requirement: &
|
16
|
+
requirement: &2164332000 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2164332000
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activesupport
|
27
|
-
requirement: &
|
27
|
+
requirement: &2164331500 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '3'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2164331500
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: cucumber
|
38
|
-
requirement: &
|
38
|
+
requirement: &2164331120 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2164331120
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bueller
|
49
|
-
requirement: &
|
49
|
+
requirement: &2164330580 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 0.0.2
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2164330580
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rake
|
60
|
-
requirement: &
|
60
|
+
requirement: &2164330160 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *2164330160
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &2164329620 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '2.0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *2164329620
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: sandbox
|
82
|
-
requirement: &
|
82
|
+
requirement: &2164329200 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *2164329200
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: timecop
|
93
|
-
requirement: &
|
93
|
+
requirement: &2164328740 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *2164328740
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: carbon
|
104
|
-
requirement: &
|
104
|
+
requirement: &2164328240 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ~>
|
@@ -109,10 +109,10 @@ dependencies:
|
|
109
109
|
version: 1.1.1
|
110
110
|
type: :runtime
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *2164328240
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: i18n
|
115
|
-
requirement: &
|
115
|
+
requirement: &2164327820 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
118
|
- - ! '>='
|
@@ -120,18 +120,18 @@ dependencies:
|
|
120
120
|
version: '0'
|
121
121
|
type: :runtime
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *2164327820
|
124
124
|
- !ruby/object:Gem::Dependency
|
125
125
|
name: dkastner-moneta
|
126
|
-
requirement: &
|
126
|
+
requirement: &2164327280 !ruby/object:Gem::Requirement
|
127
127
|
none: false
|
128
128
|
requirements:
|
129
129
|
- - ~>
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: 1.1.
|
131
|
+
version: 1.1.1
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
|
-
version_requirements: *
|
134
|
+
version_requirements: *2164327280
|
135
135
|
description: A gem for monitoring the carbon footprint of your ruby app
|
136
136
|
email: dkastner@gmail.com
|
137
137
|
executables: []
|
@@ -188,7 +188,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
188
188
|
version: '0'
|
189
189
|
segments:
|
190
190
|
- 0
|
191
|
-
hash:
|
191
|
+
hash: -3333556664513962669
|
192
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
193
|
none: false
|
194
194
|
requirements:
|
@@ -197,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
197
|
version: '0'
|
198
198
|
segments:
|
199
199
|
- 0
|
200
|
-
hash:
|
200
|
+
hash: -3333556664513962669
|
201
201
|
requirements: []
|
202
202
|
rubyforge_project:
|
203
203
|
rubygems_version: 1.8.5
|