carbon 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/carbon.gemspec +3 -1
- data/lib/carbon.rb +5 -127
- data/lib/carbon/emission_estimate.rb +65 -28
- data/lib/carbon/emission_estimate/request.rb +57 -0
- data/lib/carbon/emission_estimate/response.rb +43 -0
- data/spec/lib/carbon_spec.rb +50 -13
- metadata +5 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.6
|
data/carbon.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{carbon}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.6"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Derek Kastner", "Seamus Abshere"]
|
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
|
|
25
25
|
"lib/carbon.rb",
|
26
26
|
"lib/carbon/base.rb",
|
27
27
|
"lib/carbon/emission_estimate.rb",
|
28
|
+
"lib/carbon/emission_estimate/request.rb",
|
29
|
+
"lib/carbon/emission_estimate/response.rb",
|
28
30
|
"spec/lib/carbon_spec.rb",
|
29
31
|
"spec/spec_helper.rb",
|
30
32
|
"spec/specwatchr"
|
data/lib/carbon.rb
CHANGED
@@ -46,8 +46,6 @@ module Carbon
|
|
46
46
|
REALTIME_URL = 'http://carbon.brighterplanet.com'
|
47
47
|
ASYNC_URL = 'https://queue.amazonaws.com/121562143717/cm1_production_incoming'
|
48
48
|
|
49
|
-
class BlankCallback < ArgumentError # :nodoc:
|
50
|
-
end
|
51
49
|
class RealtimeEstimateFailed < RuntimeError # :nodoc:
|
52
50
|
end
|
53
51
|
class QueueingFailed < RuntimeError # :nodoc:
|
@@ -58,11 +56,6 @@ module Carbon
|
|
58
56
|
# The api key obtained from http://keys.brighterplanet.com
|
59
57
|
mattr_accessor :key
|
60
58
|
|
61
|
-
def self.prepare_options(options) # :nodoc:
|
62
|
-
options[:key] ||= key
|
63
|
-
options[:mode] ||= options.has_key?(:callback) ? :async : :realtime
|
64
|
-
end
|
65
|
-
|
66
59
|
# You will probably never access this module directly. Instead, you'll use it through the DSL.
|
67
60
|
#
|
68
61
|
# It's mixed into any class that includes <tt>Carbon</tt>.
|
@@ -82,123 +75,6 @@ module Carbon
|
|
82
75
|
# japanese-style preferred
|
83
76
|
alias :emits_as :emit_as
|
84
77
|
end
|
85
|
-
|
86
|
-
# Used internally, but you can look if you want.
|
87
|
-
#
|
88
|
-
# Returns the URL to which emissions estimate queries will be POSTed.
|
89
|
-
#
|
90
|
-
# For example:
|
91
|
-
# > my_car._carbon_request_url
|
92
|
-
# => 'http://carbon.brighterplanet.com/automobiles.json'
|
93
|
-
def _carbon_request_url(options = {})
|
94
|
-
::Carbon.prepare_options options
|
95
|
-
send "_#{options[:mode]}_carbon_request_url"
|
96
|
-
end
|
97
|
-
|
98
|
-
def _realtime_carbon_request_url # :nodoc:
|
99
|
-
"#{::Carbon::REALTIME_URL}/#{self.class.carbon_base.emitter_common_name.pluralize}.json"
|
100
|
-
end
|
101
|
-
|
102
|
-
def _async_carbon_request_url # :nodoc:
|
103
|
-
::Carbon::ASYNC_URL
|
104
|
-
end
|
105
|
-
|
106
|
-
# Used internally, but you can look if you want.
|
107
|
-
#
|
108
|
-
# Returns the request body that will be posted.
|
109
|
-
#
|
110
|
-
# For example:
|
111
|
-
# > my_car._carbon_request_body
|
112
|
-
# => 'fuel_efficiency=41&model=Ford+Taurus'
|
113
|
-
def _carbon_request_body(options = {})
|
114
|
-
::Carbon.prepare_options options
|
115
|
-
send "_#{options[:mode]}_carbon_request_body", options
|
116
|
-
end
|
117
|
-
|
118
|
-
def _async_carbon_request_body(options) # :nodoc:
|
119
|
-
params = _carbon_request_params options
|
120
|
-
params[:emitter] = self.class.carbon_base.emitter_common_name
|
121
|
-
raise ::Carbon::BlankCallback unless options[:callback].present?
|
122
|
-
params[:callback] = options[:callback]
|
123
|
-
params[:callback_content_type] = options[:callback_content_type] || 'application/json'
|
124
|
-
{
|
125
|
-
:Action => 'SendMessage',
|
126
|
-
:Version => '2009-02-01',
|
127
|
-
:MessageBody => params.to_query
|
128
|
-
}.to_query
|
129
|
-
end
|
130
|
-
|
131
|
-
def _realtime_carbon_request_body(options) # :nodoc:
|
132
|
-
_carbon_request_params(options).to_query
|
133
|
-
end
|
134
|
-
|
135
|
-
# Used internally, but you can look if you want.
|
136
|
-
#
|
137
|
-
# Returns the params hash that will be send to the emission estimate server.
|
138
|
-
def _carbon_request_params(options)
|
139
|
-
::Carbon.prepare_options options
|
140
|
-
params = self.class.carbon_base.translation_table.inject(Hash.new) do |memo, translation|
|
141
|
-
characteristic, as = translation
|
142
|
-
current_value = send as
|
143
|
-
if current_value.present?
|
144
|
-
if characteristic.is_a? Array # [:mixer, :size]
|
145
|
-
memo[characteristic[0]] ||= Hash.new # { :mixer => Hash.new }
|
146
|
-
memo[characteristic[0]][characteristic[1]] = current_value # { :mixer => { :size => 'foo' }}
|
147
|
-
else # :oven_count
|
148
|
-
memo[characteristic] = current_value # { :oven_count => 'bar' }
|
149
|
-
end
|
150
|
-
end
|
151
|
-
memo
|
152
|
-
end
|
153
|
-
params.merge! options.slice(:timeframe, :key)
|
154
|
-
params
|
155
|
-
end
|
156
|
-
|
157
|
-
def _realtime_emission(options = {}) # :nodoc:
|
158
|
-
attempts = 0
|
159
|
-
begin
|
160
|
-
response = _carbon_response options
|
161
|
-
raise ::Carbon::RateLimited if response.status_code == 403 and response.body =~ /Rate Limit/i
|
162
|
-
rescue ::Carbon::RateLimited
|
163
|
-
if attempts < 4
|
164
|
-
attempts += 1
|
165
|
-
sleep 0.2 * attempts
|
166
|
-
retry
|
167
|
-
else
|
168
|
-
raise $!, "Rate limited #{attempts} time(s) in a row"
|
169
|
-
end
|
170
|
-
end
|
171
|
-
raise ::Carbon::RealtimeEstimateFailed unless response.success?
|
172
|
-
::Carbon::EmissionEstimate.new ::ActiveSupport::JSON.decode(response.body)
|
173
|
-
end
|
174
|
-
|
175
|
-
def _async_emission(options = {}) # :nodoc:
|
176
|
-
response = _carbon_response options
|
177
|
-
raise ::Carbon::QueueingFailed unless response.success?
|
178
|
-
true
|
179
|
-
end
|
180
|
-
|
181
|
-
# Used internally, but you can look if you want.
|
182
|
-
#
|
183
|
-
# Runs the query and returns the raw response body, which will be in JSON.
|
184
|
-
#
|
185
|
-
# For example:
|
186
|
-
# > my_car._carbon_response.body
|
187
|
-
# => "{ 'emission' => 410.29, 'emission_units' => 'kilograms', [...] }"
|
188
|
-
def _carbon_response(options = {})
|
189
|
-
@last_carbon_request = ::REST::Request.new :post, ::URI.parse(_carbon_request_url(options)), _carbon_request_body(options), {'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'}
|
190
|
-
@last_carbon_response = @last_carbon_request.perform
|
191
|
-
end
|
192
|
-
|
193
|
-
# Returns an object representing the last emission estimate request.
|
194
|
-
def last_carbon_request
|
195
|
-
@last_carbon_request
|
196
|
-
end
|
197
|
-
|
198
|
-
# Returns an object representing the last emission estimate response.
|
199
|
-
def last_carbon_response
|
200
|
-
@last_carbon_response
|
201
|
-
end
|
202
78
|
|
203
79
|
# Returns an emission estimate.
|
204
80
|
#
|
@@ -219,8 +95,10 @@ module Carbon
|
|
219
95
|
# * <tt>:callback</tt> (optional) where to POST the result when it's been calculated. You need a server waiting for it!
|
220
96
|
# * <tt>:callback_content_type</tt> (optional if <tt>:callback</tt> is specified, ignored otherwise) pass a MIME type like 'text/yaml' so we know how to format the result when we send it to your waiting server. Defaults to 'application/json'.
|
221
97
|
# * <tt>:key</tt> (optional, overrides general <tt>Carbon</tt>.<tt>key</tt> setting just for this query) If you want to use different API keys for different queries.
|
222
|
-
def
|
223
|
-
::Carbon.
|
224
|
-
|
98
|
+
def emission_estimate(options = {})
|
99
|
+
@emission_estimate ||= ::Carbon::EmissionEstimate.new self
|
100
|
+
@emission_estimate.take_options options
|
101
|
+
@emission_estimate
|
225
102
|
end
|
103
|
+
alias :emission :emission_estimate
|
226
104
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'carbon/emission_estimate/response'
|
2
|
+
require 'carbon/emission_estimate/request'
|
3
|
+
|
1
4
|
module Carbon
|
2
5
|
# Let's start off by saying that <tt>EmissionEstimate</tt> objects quack like numbers.
|
3
6
|
#
|
@@ -7,52 +10,86 @@ module Carbon
|
|
7
10
|
#
|
8
11
|
# Note: <b>you need to take care of storing emission estimates to local variables!</b> The gem doesn't cache these for you. Every time you call <tt>emission</tt> it will send another query to the server!
|
9
12
|
class EmissionEstimate
|
13
|
+
def initialize(emitter)
|
14
|
+
@emitter = emitter
|
15
|
+
end
|
16
|
+
|
17
|
+
def take_options(options = {})
|
18
|
+
options.each do |k, v|
|
19
|
+
instance_variable_set "@#{k}", v
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# I can be compared directly to a number.
|
24
|
+
def ==(other)
|
25
|
+
case other
|
26
|
+
when Numeric
|
27
|
+
other == response.number
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# You can ask an EmissionEstimate object for any of the response data provided.
|
34
|
+
# This is useful for characteristics that are unique to an emitter.
|
35
|
+
#
|
36
|
+
# For example:
|
37
|
+
# > my_car.emission.model
|
38
|
+
# => 'Ford Taurus'
|
39
|
+
def method_missing(method_id, *args, &blk)
|
40
|
+
if !block_given? and args.empty? and response.data.has_key? method_id.to_s
|
41
|
+
response.data[method_id.to_s]
|
42
|
+
else
|
43
|
+
response.number.send method_id, *args, &blk
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_writer :callback_content_type
|
48
|
+
attr_writer :key
|
49
|
+
|
50
|
+
attr_accessor :callback
|
51
|
+
attr_accessor :timeframe
|
52
|
+
|
10
53
|
attr_reader :data
|
11
|
-
|
12
|
-
|
13
|
-
@
|
54
|
+
attr_reader :emitter
|
55
|
+
def request
|
56
|
+
@request ||= Request.new self
|
57
|
+
end
|
58
|
+
# Here's where caching takes place.
|
59
|
+
def response
|
60
|
+
current_params = request.params
|
61
|
+
@response ||= {}
|
62
|
+
return @response[current_params] if @response.has_key? current_params
|
63
|
+
@response[current_params] = Response.new self
|
64
|
+
end
|
65
|
+
def mode
|
66
|
+
callback ? :async : :realtime
|
67
|
+
end
|
68
|
+
def callback_content_type
|
69
|
+
@callback_content_type || 'application/json'
|
14
70
|
end
|
15
|
-
def
|
16
|
-
|
71
|
+
def key
|
72
|
+
@key || ::Carbon.key
|
17
73
|
end
|
18
74
|
# Another way to access the emission value.
|
19
75
|
# Useful if you don't like treating <tt>EmissionEstimate</tt> objects like <tt>Numeric</tt> objects (even though they do quack like numbers...)
|
20
76
|
def emission_value
|
21
|
-
|
77
|
+
response.number
|
22
78
|
end
|
23
79
|
# The units of the emission.
|
24
80
|
def emission_units
|
25
|
-
data['emission_units']
|
26
|
-
end
|
27
|
-
# The Timeframe the emission estimate covers.
|
28
|
-
# > my_car.emission.timeframe.to_param
|
29
|
-
# => '2009-01-01/2010-01-01'
|
30
|
-
def timeframe
|
31
|
-
Timeframe.interval data['timeframe']
|
81
|
+
response.data['emission_units']
|
32
82
|
end
|
33
83
|
# Errors (and warnings) as reported in the response.
|
34
84
|
# Note: may contain HTML tags like KBD or A
|
35
85
|
def errors
|
36
|
-
data['errors']
|
86
|
+
response.data['errors']
|
37
87
|
end
|
38
88
|
# The URL of the methodology report indicating how this estimate was calculated.
|
39
89
|
# > my_car.emission.methodology
|
40
90
|
# => 'http://carbon.brighterplanet.com/automobiles.html?[...]'
|
41
91
|
def methodology
|
42
|
-
data['methodology']
|
43
|
-
end
|
44
|
-
# You can ask an EmissionEstimate object for any of the response data provided.
|
45
|
-
# This is useful for characteristics that are unique to an emitter.
|
46
|
-
#
|
47
|
-
# For example:
|
48
|
-
# > my_car.emission.model
|
49
|
-
# => 'Ford Taurus'
|
50
|
-
def method_missing(method_id, *args, &blk)
|
51
|
-
if !block_given? and args.empty? and data.has_key? method_id.to_s
|
52
|
-
data[method_id.to_s]
|
53
|
-
else
|
54
|
-
@number.send method_id, *args, &blk
|
55
|
-
end
|
92
|
+
response.data['methodology']
|
56
93
|
end
|
57
94
|
end
|
58
95
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Carbon
|
2
|
+
class EmissionEstimate
|
3
|
+
class Request
|
4
|
+
attr_reader :parent
|
5
|
+
def initialize(parent)
|
6
|
+
@parent = parent
|
7
|
+
end
|
8
|
+
def body
|
9
|
+
send "#{parent.mode}_body"
|
10
|
+
end
|
11
|
+
def async_body # :nodoc:
|
12
|
+
params = params
|
13
|
+
params[:emitter] = parent.emitter.class.carbon_base.emitter_common_name
|
14
|
+
params[:callback] = parent.callback
|
15
|
+
params[:callback_content_type] = parent.callback_content_type
|
16
|
+
{
|
17
|
+
:Action => 'SendMessage',
|
18
|
+
:Version => '2009-02-01',
|
19
|
+
:MessageBody => params.to_query
|
20
|
+
}.to_query
|
21
|
+
end
|
22
|
+
def realtime_body # :nodoc:
|
23
|
+
params.to_query
|
24
|
+
end
|
25
|
+
# Used internally, but you can look if you want.
|
26
|
+
#
|
27
|
+
# Returns the params hash that will be send to the emission estimate server.
|
28
|
+
def params
|
29
|
+
params = parent.emitter.class.carbon_base.translation_table.inject({}) do |memo, translation|
|
30
|
+
characteristic, as = translation
|
31
|
+
current_value = parent.emitter.send as
|
32
|
+
if current_value.present?
|
33
|
+
if characteristic.is_a? Array # [:mixer, :size]
|
34
|
+
memo[characteristic[0]] ||= {} # { :mixer => Hash.new }
|
35
|
+
memo[characteristic[0]][characteristic[1]] = current_value # { :mixer => { :size => 'foo' }}
|
36
|
+
else # :oven_count
|
37
|
+
memo[characteristic] = current_value # { :oven_count => 'bar' }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
memo
|
41
|
+
end
|
42
|
+
params[:timeframe] = parent.timeframe
|
43
|
+
params[:key] = parent.key
|
44
|
+
params
|
45
|
+
end
|
46
|
+
def realtime_url # :nodoc:
|
47
|
+
"#{::Carbon::REALTIME_URL}/#{parent.emitter.class.carbon_base.emitter_common_name.pluralize}.json"
|
48
|
+
end
|
49
|
+
def async_url # :nodoc:
|
50
|
+
::Carbon::ASYNC_URL
|
51
|
+
end
|
52
|
+
def url
|
53
|
+
send "#{parent.mode}_url"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Carbon
|
2
|
+
class EmissionEstimate
|
3
|
+
class Response
|
4
|
+
attr_reader :parent
|
5
|
+
attr_reader :data
|
6
|
+
attr_reader :number
|
7
|
+
attr_reader :raw_request
|
8
|
+
attr_reader :raw_response
|
9
|
+
def initialize(parent)
|
10
|
+
@parent = parent
|
11
|
+
send "load_#{parent.mode}_data"
|
12
|
+
end
|
13
|
+
def load_realtime_data # :nodoc:
|
14
|
+
attempts = 0
|
15
|
+
begin
|
16
|
+
response = perform
|
17
|
+
raise ::Carbon::RateLimited if response.status_code == 403 and response.body =~ /Rate Limit/i
|
18
|
+
rescue ::Carbon::RateLimited
|
19
|
+
if attempts < 4
|
20
|
+
attempts += 1
|
21
|
+
sleep 0.2 * attempts
|
22
|
+
retry
|
23
|
+
else
|
24
|
+
raise $!, "Rate limited #{attempts} time(s) in a row"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
raise ::Carbon::RealtimeEstimateFailed unless response.success?
|
28
|
+
@data = ::ActiveSupport::JSON.decode response.body
|
29
|
+
@number = data['emission'].to_f.freeze
|
30
|
+
end
|
31
|
+
def load_async_data # :nodoc:
|
32
|
+
response = perform
|
33
|
+
raise ::Carbon::QueueingFailed unless response.success?
|
34
|
+
@data = {}
|
35
|
+
@number = nil
|
36
|
+
end
|
37
|
+
def perform # :nodoc:
|
38
|
+
@raw_request = ::REST::Request.new :post, ::URI.parse(parent.request.url), parent.request.body, {'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'}
|
39
|
+
@raw_response = @raw_request.perform
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/lib/carbon_spec.rb
CHANGED
@@ -33,53 +33,90 @@ describe Carbon do
|
|
33
33
|
c.model = 'Acura'
|
34
34
|
c.model_year = 2003
|
35
35
|
c.fuel_economy = 32
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
c.emission.should == 134.599
|
37
|
+
c.emission.emission_units.should == 'kilograms'
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'caching' do
|
41
|
+
it "should keep around estimates if the parameters don't change" do
|
42
|
+
c = RentalCar.new
|
43
|
+
c.model = 'Acura'
|
44
|
+
c.model_year = 2003
|
45
|
+
c.fuel_economy = 32
|
46
|
+
c.emission.should == 134.599
|
47
|
+
first_raw_request = c.emission.response.raw_request
|
48
|
+
c.emission.should == 134.599
|
49
|
+
c.emission.response.raw_request.object_id.should == first_raw_request.object_id
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should recalculate if parameters change" do
|
53
|
+
c = RentalCar.new
|
54
|
+
c.model = 'Acura'
|
55
|
+
c.model_year = 2003
|
56
|
+
c.fuel_economy = 32
|
57
|
+
c.emission.should == 134.599
|
58
|
+
first_raw_request = c.emission.response.raw_request
|
59
|
+
c.model = 'Honda'
|
60
|
+
c.emission.should == 134.599
|
61
|
+
c.emission.response.raw_request.object_id.should_not == first_raw_request.object_id
|
62
|
+
end
|
39
63
|
end
|
40
64
|
|
41
65
|
describe 'synchronous (realtime) requests' do
|
42
66
|
it 'should handle complex attributes like mixer[size]' do
|
43
67
|
d = DonutFactory.new
|
44
68
|
d.mixer_size = 20
|
45
|
-
d.
|
69
|
+
d.emission.request.body.should =~ /mixer\[size\]=20/
|
46
70
|
end
|
47
71
|
|
48
72
|
it 'should not send attributes that are blank' do
|
49
73
|
d = DonutFactory.new
|
50
74
|
d.mixer_size = 20
|
51
|
-
d.
|
75
|
+
d.emission.request.body.should_not =~ /oven_count/
|
52
76
|
end
|
53
77
|
|
54
78
|
it 'should send the key' do
|
55
79
|
d = DonutFactory.new
|
56
|
-
d.
|
80
|
+
d.emission.request.body.should =~ /key=valid/
|
57
81
|
end
|
58
82
|
|
59
83
|
it 'should override defaults' do
|
60
84
|
d = DonutFactory.new
|
61
|
-
|
62
|
-
d.
|
85
|
+
key = 'ADifferentOne'
|
86
|
+
d.emission.key.should == 'valid'
|
87
|
+
d.emission.key = key
|
88
|
+
d.emission.key.should == key
|
63
89
|
end
|
64
90
|
|
65
91
|
it 'should accept timeframes' do
|
66
92
|
c = RentalCar.new
|
67
|
-
|
68
|
-
c.
|
93
|
+
t = Timeframe.new(:year => 2009)
|
94
|
+
c.emission.timeframe = t
|
95
|
+
c.emission.timeframe.should == t
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should accept timeframes inline' do
|
99
|
+
c = RentalCar.new
|
100
|
+
t = Timeframe.new(:year => 2009)
|
101
|
+
c.emission(:timeframe => t)
|
102
|
+
c.emission.timeframe.should == t
|
69
103
|
end
|
70
104
|
|
71
105
|
it 'should not generate post bodies with lots of empty params' do
|
72
106
|
c = RentalCar.new
|
73
107
|
c.emission :timeframe => Timeframe.new(:year => 2009)
|
74
|
-
c.
|
108
|
+
c.emission.request.body.should_not include('&&')
|
75
109
|
end
|
76
110
|
end
|
77
111
|
|
78
112
|
describe 'asynchronous (queued) requests' do
|
79
113
|
it 'should post a message to SQS' do
|
80
114
|
c = RentalCar.new
|
81
|
-
c.
|
82
|
-
c.emission
|
115
|
+
c.emission.callback = 'http://www.postbin.org/1dj0146'
|
116
|
+
c.emission.request.url.should =~ /queue.amazonaws.com/
|
117
|
+
lambda {
|
118
|
+
c.emission :timeframe => Timeframe.new(:year => 2009), :callback => 'http://www.postbin.org/1dj0146'
|
119
|
+
}.should_not raise_error
|
83
120
|
end
|
84
121
|
end
|
85
122
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: carbon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 6
|
10
|
+
version: 0.1.6
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Derek Kastner
|
@@ -118,6 +118,8 @@ files:
|
|
118
118
|
- lib/carbon.rb
|
119
119
|
- lib/carbon/base.rb
|
120
120
|
- lib/carbon/emission_estimate.rb
|
121
|
+
- lib/carbon/emission_estimate/request.rb
|
122
|
+
- lib/carbon/emission_estimate/response.rb
|
121
123
|
- spec/lib/carbon_spec.rb
|
122
124
|
- spec/spec_helper.rb
|
123
125
|
- spec/specwatchr
|