carbon 1.1.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|