carbon 0.1.5 → 0.1.6
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/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
|