carbon 1.1.3 → 2.0.0
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/.gitignore +4 -22
- data/CHANGELOG +11 -0
- data/Gemfile +10 -1
- data/README.markdown +185 -0
- data/Rakefile +13 -26
- data/bin/carbon +3 -3
- data/carbon.gemspec +17 -23
- data/developer_notes/MULTI.markdown +25 -0
- data/developer_notes/REDUCE_HTTP_CONNECTIONS.markdown +46 -0
- data/features/shell.feature +1 -1
- data/features/support/env.rb +3 -4
- data/lib/carbon.rb +242 -96
- data/lib/carbon/registry.rb +50 -0
- data/lib/carbon/shell.rb +14 -8
- data/lib/carbon/shell/emitter.rb +33 -29
- data/lib/carbon/version.rb +1 -1
- data/test/carbon_test.rb +167 -0
- metadata +128 -182
- data/MIT-LICENSE.txt +0 -19
- data/README.rdoc +0 -266
- data/doc/INTEGRATION_GUIDE.rdoc +0 -1002
- data/doc/examining-response-with-jsonview.png +0 -0
- data/doc/shell_example +0 -43
- data/doc/timeout-error.png +0 -0
- data/doc/with-committee-reports.png +0 -0
- data/doc/without-committee-reports.png +0 -0
- data/lib/carbon/base.rb +0 -62
- data/lib/carbon/emission_estimate.rb +0 -165
- data/lib/carbon/emission_estimate/request.rb +0 -100
- data/lib/carbon/emission_estimate/response.rb +0 -61
- data/lib/carbon/emission_estimate/storage.rb +0 -33
- data/spec/fixtures/vcr_cassettes/flight.yml +0 -47
- data/spec/fixtures/vcr_cassettes/residence.yml +0 -44
- data/spec/lib/carbon/emission_estimate/request_spec.rb +0 -41
- data/spec/lib/carbon/emission_estimate/response_spec.rb +0 -33
- data/spec/lib/carbon/emission_estimate_spec.rb +0 -32
- data/spec/lib/carbon_spec.rb +0 -384
- data/spec/spec_helper.rb +0 -60
- data/spec/specwatchr +0 -60
Binary file
|
data/doc/shell_example
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
carbon> key '123abc'
|
2
|
-
=> Using key 123abc
|
3
|
-
carbon> help
|
4
|
-
=> flight, automobile, ...
|
5
|
-
carbon> flight
|
6
|
-
=> 974 kg CO2e
|
7
|
-
flight> lbs
|
8
|
-
=> 123 lbs CO2e
|
9
|
-
flight> tons
|
10
|
-
=> 1 ton CO2e
|
11
|
-
flight> help
|
12
|
-
=> origin_airport, destination_airport . . .
|
13
|
-
flight> origin_airport 'lax'
|
14
|
-
=> 1123 kg CO2e
|
15
|
-
flight> destination_airport 'jfk'
|
16
|
-
=> 1375 kg CO2e
|
17
|
-
flight> characteristics
|
18
|
-
=> Origin airport: JFK
|
19
|
-
Destination airport: JFK
|
20
|
-
flight> methodology
|
21
|
-
=> Emission: from foo and bar
|
22
|
-
Foo: from baz
|
23
|
-
Bar: default
|
24
|
-
flight> url
|
25
|
-
=> http://carbon.brighterplanet.com/flights?key=123abc&origin_airport[iata_code]=lax&destination_airport[iata_code]=jfk
|
26
|
-
flight> done
|
27
|
-
=> Origin airport: JFK
|
28
|
-
Destination airport: JFK
|
29
|
-
1375 kg CO2e
|
30
|
-
http://carbon.brighterplanet.com/flights?key=123abc&origin_airport[iata_code]=lax&destination_airport[iata_code]=jfk
|
31
|
-
Saved as flight #1
|
32
|
-
carbon> flight 1
|
33
|
-
=> Origin airport: JFK
|
34
|
-
Destination airport: JFK
|
35
|
-
1375 kg CO2e
|
36
|
-
flight> done
|
37
|
-
=> Origin airport: JFK
|
38
|
-
Destination airport: JFK
|
39
|
-
1375 kg CO2e
|
40
|
-
http://carbon.brighterplanet.com/flights?key=123abc&origin_airport[iata_code]=lax&destination_airport[iata_code]=jfk
|
41
|
-
Saved as flight #1
|
42
|
-
carbon> exit
|
43
|
-
$
|
data/doc/timeout-error.png
DELETED
Binary file
|
Binary file
|
Binary file
|
data/lib/carbon/base.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
module Carbon
|
2
|
-
# You will probably never access this class directly. Instead, you'll touch it through the DSL.
|
3
|
-
#
|
4
|
-
# An instance of this appears on any class that includes <tt>Carbon</tt>.
|
5
|
-
class Base
|
6
|
-
include Blockenspiel::DSL
|
7
|
-
attr_reader :emitter_common_name
|
8
|
-
|
9
|
-
def initialize(emitter_common_name)
|
10
|
-
@emitter_common_name = emitter_common_name.to_s
|
11
|
-
end
|
12
|
-
|
13
|
-
# A completed translation table will look like:
|
14
|
-
# {[:mixer, :size]=>"mixer_size",
|
15
|
-
# :personnel=>:employees,
|
16
|
-
# :smokestack_size=>"smokestack_size",
|
17
|
-
# :oven_count=>"oven_count",
|
18
|
-
# [:mixer, :wattage]=>"mixer_wattage"}
|
19
|
-
def translation_table # :nodoc:
|
20
|
-
@translation_table ||= Hash.new
|
21
|
-
end
|
22
|
-
|
23
|
-
def reset_translation_table! #:nodoc:
|
24
|
-
@translation_table = Hash.new
|
25
|
-
end
|
26
|
-
|
27
|
-
# Indicate that you will send in a piece of data about the emitter.
|
28
|
-
#
|
29
|
-
# Two general rules:
|
30
|
-
# * Take note of what Brighter Planet expects to receive. If you send <tt>fuel_economy</tt> and we're expecting <tt>fuel_efficiency</tt>, we won't understand! (Hint: use the <tt>:as</tt> option.)
|
31
|
-
# * Make sure <tt>#to_characteristic</tt> or <tt>#to_param</tt> is set up. The gem always calls one of these (it will try <tt>#to_characteristic</tt> first) before sending, so that's your change to key things by "EPA code" (fictional) or whatever else you want. (Hint: use the <tt>:key</tt> option.)
|
32
|
-
#
|
33
|
-
# There are two optional parameters:
|
34
|
-
# * <tt>:as</tt> - if Brighter Planet expects <tt>fuel_efficiency</tt>, and you say <tt>mpg</tt>, you can write <tt>provide :mpg, :as => :fuel_efficiency</tt>. This will result in a query like <tt>?fuel_efficiency=XYZ</tt>.
|
35
|
-
# * <tt>:key</tt> - if Brighter Planet expects a make's name ("Nissan") and you want to look things up by (fictional) "EPA code", you could say <tt>provide :make, :key => :epa_code</tt>. This will result in a query like <tt>?make[epa_code]=ABC</tt>.
|
36
|
-
#
|
37
|
-
# # What's sent to Brighter Planet
|
38
|
-
# emit_as :automobile do
|
39
|
-
# provide :mpg, :as => :fuel_efficiency # fuel_efficiency=my_car.mpg.to_param
|
40
|
-
# provide :make, :key => :epa_code # make[epa_code]=my_car.make.to_param
|
41
|
-
# # or really shaking things up...
|
42
|
-
# provide :manufacturer, :as => :make, :key => :epa_code # make[epa_code]=my_car.manufacturer.to_param
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# Note that no matter what you send to us, the gem always calls <b><tt>#to_characteristic</tt></b> (or, failing that, <tt>#to_param</tt>) on the emitter. In this example, it's up to you to make sure my_car.manufacturer.to_param returns an epa_code.
|
46
|
-
def provide(attr_name, options = {})
|
47
|
-
options = options.symbolize_keys
|
48
|
-
characteristic = if options.has_key? :as
|
49
|
-
# [ :mpg, :fuel_efficiency ]
|
50
|
-
[attr_name, options[:as]]
|
51
|
-
else
|
52
|
-
# :make
|
53
|
-
attr_name
|
54
|
-
end
|
55
|
-
# translation_table[:make] = 'epa_code'
|
56
|
-
translation_table[characteristic] = options[:key]
|
57
|
-
end
|
58
|
-
|
59
|
-
# Third-person singular preferred.
|
60
|
-
alias :provides :provide
|
61
|
-
end
|
62
|
-
end
|
@@ -1,165 +0,0 @@
|
|
1
|
-
module Carbon
|
2
|
-
# Let's start off by saying that realtime <tt>EmissionEstimate</tt> objects quack like numbers.
|
3
|
-
#
|
4
|
-
# If you ask for a callback, on the other hand, you can't use them as numbers.
|
5
|
-
#
|
6
|
-
# So, you can just say <tt>my_car.emission_estimate.to_s</tt> and you'll get something like <tt>"4308.29"</tt>.
|
7
|
-
#
|
8
|
-
# At the same time, they contain all the data you get back from the emission estimate web service. For example, you could say <tt>puts my_donut_factor.emission_estimate.oven_count</tt> (see the tests) and you'd get back the oven count used in the calculation, if any.
|
9
|
-
class EmissionEstimate
|
10
|
-
autoload :Response, 'carbon/emission_estimate/response'
|
11
|
-
autoload :Request, 'carbon/emission_estimate/request'
|
12
|
-
autoload :Storage, 'carbon/emission_estimate/storage'
|
13
|
-
|
14
|
-
def self.parse(str) #:nodoc:
|
15
|
-
data = ::ActiveSupport::JSON.decode str
|
16
|
-
if data.has_key? 'active_subtimeframe'
|
17
|
-
interval = "#{data['active_subtimeframe']['startDate']}/#{data['active_subtimeframe']['endDate']}"
|
18
|
-
data['active_subtimeframe'] = Timeframe.interval(interval)
|
19
|
-
end
|
20
|
-
data['updated_at'] = ::Time.parse(data['updated_at']) if data.has_key?('updated_at') and data['updated_at'].is_a?(::String)
|
21
|
-
data
|
22
|
-
end
|
23
|
-
|
24
|
-
attr_writer :callback_content_type
|
25
|
-
attr_writer :key
|
26
|
-
attr_writer :timeout
|
27
|
-
attr_writer :defer
|
28
|
-
attr_accessor :callback
|
29
|
-
attr_accessor :timeframe
|
30
|
-
attr_accessor :certified
|
31
|
-
attr_accessor :guid
|
32
|
-
attr_accessor :comply
|
33
|
-
attr_reader :emitter
|
34
|
-
|
35
|
-
def initialize(emitter, options = {})
|
36
|
-
@emitter = emitter
|
37
|
-
take_options options unless options.empty?
|
38
|
-
end
|
39
|
-
|
40
|
-
VALID_OPTIONS = [:callback_content_type, :key, :callback, :timeframe, :guid, :timeout, :defer, :certified, :comply]
|
41
|
-
|
42
|
-
def take_options(options) #:nodoc:
|
43
|
-
return if options.blank?
|
44
|
-
options.slice(*VALID_OPTIONS).each do |k, v|
|
45
|
-
instance_variable_set "@#{k}", v
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# I can be compared directly to a number, unless I'm an async request.
|
50
|
-
def ==(other)
|
51
|
-
if other.is_a? ::Numeric and mode == :realtime
|
52
|
-
other == number
|
53
|
-
else
|
54
|
-
super
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# You can ask an EmissionEstimate object for any of the response data provided.
|
59
|
-
# This is useful for characteristics that are unique to an emitter.
|
60
|
-
#
|
61
|
-
# For example:
|
62
|
-
# > my_car.emission_estimate.model
|
63
|
-
# => 'Ford Taurus'
|
64
|
-
def method_missing(method_id, *args, &blk)
|
65
|
-
if !block_given? and args.empty? and data.has_key? method_id.to_s
|
66
|
-
data[method_id.to_s]
|
67
|
-
elsif ::Float.method_defined? method_id
|
68
|
-
raise TriedToUseAsyncResponseAsNumber if mode == :async
|
69
|
-
number.send method_id, *args, &blk
|
70
|
-
else
|
71
|
-
super
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def data #:nodoc:
|
76
|
-
if storage.present?
|
77
|
-
storage.data
|
78
|
-
else
|
79
|
-
response.data
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def storage #:nodoc:
|
84
|
-
@storage ||= {}
|
85
|
-
return @storage[guid] if @storage.has_key? guid
|
86
|
-
@storage[guid] = Storage.new self
|
87
|
-
end
|
88
|
-
|
89
|
-
def request #:nodoc:
|
90
|
-
@request ||= Request.new self
|
91
|
-
end
|
92
|
-
|
93
|
-
# Here's where caching takes place.
|
94
|
-
def response #:nodoc:
|
95
|
-
current_params = request.params
|
96
|
-
@response ||= {}
|
97
|
-
return @response[current_params] if @response.has_key? current_params
|
98
|
-
response_object = Response.new self
|
99
|
-
response_object.load_data
|
100
|
-
@response[current_params] = response_object
|
101
|
-
end
|
102
|
-
|
103
|
-
def certified? #:nodoc:
|
104
|
-
!!certified
|
105
|
-
end
|
106
|
-
|
107
|
-
def defer? #:nodoc:
|
108
|
-
@defer == true
|
109
|
-
end
|
110
|
-
|
111
|
-
def async? #:nodoc:
|
112
|
-
callback or defer?
|
113
|
-
end
|
114
|
-
|
115
|
-
def mode #:nodoc:
|
116
|
-
async? ? :async : :realtime
|
117
|
-
end
|
118
|
-
|
119
|
-
# Timeout on realtime requests in seconds, if desired.
|
120
|
-
def timeout
|
121
|
-
@timeout
|
122
|
-
end
|
123
|
-
|
124
|
-
def callback_content_type
|
125
|
-
@callback_content_type || 'application/json'
|
126
|
-
end
|
127
|
-
|
128
|
-
def key
|
129
|
-
@key || ::Carbon.key
|
130
|
-
end
|
131
|
-
|
132
|
-
# The timeframe being looked at in the emission calculation.
|
133
|
-
def active_subtimeframe
|
134
|
-
data['active_subtimeframe']
|
135
|
-
end
|
136
|
-
|
137
|
-
# Another way to access the emission value.
|
138
|
-
# Useful if you don't like treating <tt>EmissionEstimate</tt> objects like <tt>Numeric</tt> objects (even though they do quack like numbers...)
|
139
|
-
def number
|
140
|
-
async? ? nil : data['emission'].to_f.freeze
|
141
|
-
end
|
142
|
-
|
143
|
-
# The units of the emission.
|
144
|
-
def emission_units
|
145
|
-
data['emission_units']
|
146
|
-
end
|
147
|
-
|
148
|
-
# Errors (and warnings) as reported in the response.
|
149
|
-
# Note: may contain HTML tags like KBD or A
|
150
|
-
def errors
|
151
|
-
data['errors']
|
152
|
-
end
|
153
|
-
|
154
|
-
# The URL of the methodology report indicating how this estimate was calculated.
|
155
|
-
# > my_car.emission_estimate.methodology
|
156
|
-
# => 'http://carbon.brighterplanet.com/automobiles.html?[...]'
|
157
|
-
def methodology
|
158
|
-
data['methodology']
|
159
|
-
end
|
160
|
-
|
161
|
-
def reports
|
162
|
-
data['reports']
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
@@ -1,100 +0,0 @@
|
|
1
|
-
module Carbon
|
2
|
-
class EmissionEstimate
|
3
|
-
class Request #:nodoc:all
|
4
|
-
attr_reader :parent
|
5
|
-
|
6
|
-
def initialize(parent)
|
7
|
-
@parent = parent
|
8
|
-
end
|
9
|
-
|
10
|
-
def body
|
11
|
-
params.to_query
|
12
|
-
end
|
13
|
-
|
14
|
-
def params
|
15
|
-
params = send "#{parent.mode}_params"
|
16
|
-
validate params
|
17
|
-
params
|
18
|
-
end
|
19
|
-
|
20
|
-
def validate(params_hash)
|
21
|
-
unless params_hash.key? :key
|
22
|
-
Carbon.warn 'You have not specified an API key. Please obtain a key from http://keys.brighterplanet.com.'
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def async_params
|
27
|
-
raise ::ArgumentError, "When using :callback you cannot specify :defer" if parent.defer? and parent.callback
|
28
|
-
raise ::ArgumentError, "When using :defer => true you must specify :guid" if parent.defer? and parent.guid.blank?
|
29
|
-
hash = _params
|
30
|
-
hash[:emitter] = parent.emitter.class.carbon_base.emitter_common_name
|
31
|
-
hash[:callback] = parent.callback if parent.callback
|
32
|
-
hash[:callback_content_type] = parent.callback_content_type if parent.callback
|
33
|
-
hash[:guid] = parent.guid if parent.defer?
|
34
|
-
{
|
35
|
-
:Action => 'SendMessage',
|
36
|
-
:Version => '2009-02-01',
|
37
|
-
:MessageBody => hash.to_query
|
38
|
-
}
|
39
|
-
end
|
40
|
-
|
41
|
-
def realtime_params
|
42
|
-
_params
|
43
|
-
end
|
44
|
-
|
45
|
-
# Used internally, but you can look if you want.
|
46
|
-
#
|
47
|
-
# Returns the params hash that will be send to the emission estimate server.
|
48
|
-
def _params
|
49
|
-
hash = parent.emitter.class.carbon_base.translation_table.inject({}) do |memo, translation|
|
50
|
-
characteristic, key = translation
|
51
|
-
if characteristic.is_a? ::Array
|
52
|
-
current_value = parent.emitter.send characteristic[0]
|
53
|
-
as = characteristic[1]
|
54
|
-
else
|
55
|
-
current_value = parent.emitter.send characteristic
|
56
|
-
as = characteristic
|
57
|
-
end
|
58
|
-
current_value = begin
|
59
|
-
current_value.to_characteristic
|
60
|
-
rescue NoMethodError
|
61
|
-
current_value.to_param
|
62
|
-
end
|
63
|
-
if current_value.is_a?(FalseClass) or current_value.present?
|
64
|
-
if key
|
65
|
-
memo[as] ||= {}
|
66
|
-
memo[as][key] = current_value
|
67
|
-
else
|
68
|
-
memo[as] = current_value
|
69
|
-
end
|
70
|
-
end
|
71
|
-
memo
|
72
|
-
end
|
73
|
-
hash[:timeframe] = parent.timeframe if parent.timeframe
|
74
|
-
hash[:key] = parent.key if parent.key
|
75
|
-
hash[:comply] = parent.comply if parent.comply
|
76
|
-
hash
|
77
|
-
end
|
78
|
-
|
79
|
-
def realtime_url
|
80
|
-
uri = ::URI.parse ''
|
81
|
-
uri.scheme = 'http'
|
82
|
-
uri.host = parent.certified? ? 'certified.carbon.brighterplanet.com' : 'carbon.brighterplanet.com'
|
83
|
-
uri.path = "/#{parent.emitter.class.carbon_base.emitter_common_name.pluralize}.json"
|
84
|
-
uri.to_s
|
85
|
-
end
|
86
|
-
|
87
|
-
def async_url
|
88
|
-
uri = ::URI.parse ''
|
89
|
-
uri.scheme = 'https'
|
90
|
-
uri.host = 'queue.amazonaws.com'
|
91
|
-
uri.path = parent.certified? ? '/121562143717/cm1_production_incoming_certified' : '/121562143717/cm1_production_incoming'
|
92
|
-
uri.to_s
|
93
|
-
end
|
94
|
-
|
95
|
-
def url
|
96
|
-
send "#{parent.mode}_url"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
module Carbon
|
2
|
-
class EmissionEstimate
|
3
|
-
class Response #:nodoc:all
|
4
|
-
attr_reader :parent
|
5
|
-
attr_reader :data
|
6
|
-
attr_reader :raw_request
|
7
|
-
attr_reader :raw_response
|
8
|
-
|
9
|
-
def initialize(parent)
|
10
|
-
@parent = parent
|
11
|
-
end
|
12
|
-
|
13
|
-
def load_data
|
14
|
-
send "load_#{parent.mode}_data"
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
def load_realtime_data
|
19
|
-
attempts = 0
|
20
|
-
response = nil
|
21
|
-
begin
|
22
|
-
response = perform
|
23
|
-
raise ::Carbon::RateLimited if response.status_code == 403 and response.body =~ /Rate Limit/i #TODO: Should we expect an HTTP 402: payment required, instead?
|
24
|
-
rescue ::Carbon::RateLimited
|
25
|
-
if attempts < 4
|
26
|
-
attempts += 1
|
27
|
-
sleep 0.2 * attempts
|
28
|
-
retry
|
29
|
-
else
|
30
|
-
raise $!
|
31
|
-
end
|
32
|
-
end
|
33
|
-
raise ::Carbon::RealtimeEstimateFailed unless response.success? #TODO: should we expect 300s as well as 200s? Also, we may want to include response code and body in our exception.
|
34
|
-
@data = ::Carbon::EmissionEstimate.parse response.body
|
35
|
-
end
|
36
|
-
|
37
|
-
def load_async_data
|
38
|
-
response = perform
|
39
|
-
raise ::Carbon::QueueingFailed unless response.success? #TODO: should we expect 300s as well as 200s? Also, we may want to include response code and body in our exception.
|
40
|
-
@data = {}
|
41
|
-
end
|
42
|
-
|
43
|
-
def perform
|
44
|
-
response = nil
|
45
|
-
if parent.timeout
|
46
|
-
Timeout.timeout(parent.timeout) do
|
47
|
-
response = perform_request
|
48
|
-
end
|
49
|
-
else
|
50
|
-
response = perform_request
|
51
|
-
end
|
52
|
-
response
|
53
|
-
end
|
54
|
-
|
55
|
-
def perform_request
|
56
|
-
@raw_request = ::REST::Request.new :post, ::URI.parse(parent.request.url), parent.request.body, {'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'}
|
57
|
-
@raw_response = raw_request.perform
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module Carbon
|
2
|
-
class EmissionEstimate
|
3
|
-
class Storage #:nodoc:all
|
4
|
-
attr_accessor :parent
|
5
|
-
attr_reader :raw_request
|
6
|
-
attr_reader :raw_response
|
7
|
-
|
8
|
-
def initialize(parent)
|
9
|
-
@parent = parent
|
10
|
-
end
|
11
|
-
|
12
|
-
def url
|
13
|
-
"http://storage.carbon.brighterplanet.com/#{::Digest::SHA1.hexdigest(parent.key+parent.guid)}"
|
14
|
-
end
|
15
|
-
|
16
|
-
def present?
|
17
|
-
parent.guid.present? and data.present?
|
18
|
-
end
|
19
|
-
|
20
|
-
def data
|
21
|
-
return @data[0] if @data.is_a? ::Array
|
22
|
-
@raw_request = ::REST::Request.new :get, ::URI.parse(url)
|
23
|
-
@raw_response = raw_request.perform
|
24
|
-
if raw_response.success?
|
25
|
-
@data = [::Carbon::EmissionEstimate.parse(raw_response.body)]
|
26
|
-
else
|
27
|
-
@data = []
|
28
|
-
end
|
29
|
-
@data[0]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|